MediaWiki  master
Skin.php
Go to the documentation of this file.
1 <?php
23 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
27 use Wikimedia\WrappedString;
28 use Wikimedia\WrappedStringList;
29 
42 abstract class Skin extends ContextSource {
43  use ProtectedHookAccessorTrait;
44 
48  protected $skinname = null;
49 
53  protected $options = [];
54  protected $mRelevantTitle = null;
55  protected $mRelevantUser = null;
56 
61  public $stylename = null;
62 
69  public static function getSkinNames() {
70  wfDeprecated( __METHOD__, '1.36' );
71 
72  $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
73  return $skinFactory->getSkinNames();
74  }
75 
85  public static function getAllowedSkins() {
86  wfDeprecated( __METHOD__, '1.36' );
87 
88  $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
89  return $skinFactory->getAllowedSkins();
90  }
91 
101  public static function normalizeKey( $key ) {
103 
104  $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
105  $skinNames = $skinFactory->getSkinNames();
106 
107  // Make keys lowercase for case-insensitive matching.
108  $skinNames = array_change_key_case( $skinNames, CASE_LOWER );
109  $key = strtolower( $key );
110  $defaultSkin = strtolower( $wgDefaultSkin );
111  $fallbackSkin = strtolower( $wgFallbackSkin );
112 
113  if ( $key == '' || $key == 'default' ) {
114  // Don't return the default immediately;
115  // in a misconfiguration we need to fall back.
116  $key = $defaultSkin;
117  }
118 
119  if ( isset( $skinNames[$key] ) ) {
120  return $key;
121  }
122 
123  // Older versions of the software used a numeric setting
124  // in the user preferences.
125  $fallback = [
126  0 => $defaultSkin,
127  2 => 'cologneblue'
128  ];
129 
130  if ( isset( $fallback[$key] ) ) {
131  $key = $fallback[$key];
132  }
133 
134  if ( isset( $skinNames[$key] ) ) {
135  return $key;
136  } elseif ( isset( $skinNames[$defaultSkin] ) ) {
137  return $defaultSkin;
138  } else {
139  return $fallbackSkin;
140  }
141  }
142 
153  public function __construct( $options = null ) {
154  if ( is_string( $options ) ) {
155  $this->skinname = $options;
156  } elseif ( $options ) {
157  $name = $options['name'] ?? null;
158  if ( !$name ) {
159  throw new SkinException( 'Skin name must be specified' );
160  }
161  $this->options = $options;
162  $this->skinname = $name;
163  }
164  }
165 
169  public function getSkinName() {
170  return $this->skinname;
171  }
172 
182  public function isResponsive() {
183  return $this->options['responsive'] ?? false;
184  }
185 
190  public function initPage( OutputPage $out ) {
191  $this->preloadExistence();
192 
193  if ( $this->isResponsive() ) {
194  $out->addMeta(
195  'viewport',
196  'width=device-width, initial-scale=1.0, ' .
197  'user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0'
198  );
199 
200  }
201  }
202 
214  public function getDefaultModules() {
215  $out = $this->getOutput();
216  $user = $this->getUser();
217 
218  // Modules declared in the $modules literal are loaded
219  // for ALL users, on ALL pages, in ALL skins.
220  // Keep this list as small as possible!
221  $modules = [
222  // The 'styles' key sets render-blocking style modules
223  // Unlike other keys in $modules, this is an associative array
224  // where each key is its own group pointing to a list of modules
225  'styles' => [
226  'skin' => $this->options['styles'] ?? [],
227  'core' => [],
228  'content' => [],
229  'syndicate' => [],
230  ],
231  'core' => [
232  'site',
233  'mediawiki.page.ready',
234  ],
235  // modules that enhance the content in some way
236  'content' => [],
237  // modules relating to search functionality
238  'search' => [],
239  // Skins can register their own scripts
240  'skin' => $this->options['scripts'] ?? [],
241  // modules relating to functionality relating to watching an article
242  'watch' => [],
243  // modules which relate to the current users preferences
244  'user' => [],
245  // modules relating to RSS/Atom Feeds
246  'syndicate' => [],
247  ];
248 
249  // Preload jquery.tablesorter for mediawiki.page.ready
250  if ( strpos( $out->getHTML(), 'sortable' ) !== false ) {
251  $modules['content'][] = 'jquery.tablesorter';
252  $modules['styles']['content'][] = 'jquery.tablesorter.styles';
253  }
254 
255  // Preload jquery.makeCollapsible for mediawiki.page.ready
256  if ( strpos( $out->getHTML(), 'mw-collapsible' ) !== false ) {
257  $modules['content'][] = 'jquery.makeCollapsible';
258  $modules['styles']['content'][] = 'jquery.makeCollapsible.styles';
259  }
260 
261  // Deprecated since 1.26: Unconditional loading of mediawiki.ui.button
262  // on every page is deprecated. Express a dependency instead.
263  if ( strpos( $out->getHTML(), 'mw-ui-button' ) !== false ) {
264  $modules['styles']['content'][] = 'mediawiki.ui.button';
265  }
266 
267  if ( $out->isTOCEnabled() ) {
268  $modules['content'][] = 'mediawiki.toc';
269  $modules['styles']['content'][] = 'mediawiki.toc.styles';
270  }
271 
272  $prefMgr = MediaWikiServices::getInstance()->getPermissionManager();
273  if ( $user->isLoggedIn()
274  && $prefMgr->userHasAllRights( $user, 'writeapi', 'viewmywatchlist', 'editmywatchlist' )
275  && $this->getRelevantTitle()->canExist()
276  ) {
277  $modules['watch'][] = 'mediawiki.page.watch.ajax';
278  }
279 
280  if ( $user->getBoolOption( 'editsectiononrightclick' )
281  || ( $out->isArticle() && $user->getOption( 'editondblclick' ) )
282  ) {
283  $modules['user'][] = 'mediawiki.misc-authed-pref';
284  }
285 
286  if ( $out->isSyndicated() ) {
287  $modules['styles']['syndicate'][] = 'mediawiki.feedlink';
288  }
289 
290  return $modules;
291  }
292 
296  protected function preloadExistence() {
297  $titles = [];
298 
299  // User/talk link
300  $user = $this->getUser();
301  if ( $user->isLoggedIn() ) {
302  $titles[] = $user->getUserPage();
303  $titles[] = $user->getTalkPage();
304  }
305 
306  // Check, if the page can hold some kind of content, otherwise do nothing
307  $title = $this->getRelevantTitle();
308  if ( $title->canExist() && $title->canHaveTalkPage() ) {
309  $namespaceInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
310  if ( $title->isTalkPage() ) {
311  $titles[] = $namespaceInfo->getSubjectPage( $title );
312  } else {
313  $titles[] = $namespaceInfo->getTalkPage( $title );
314  }
315  }
316 
317  // Footer links (used by SkinTemplate::prepareQuickTemplate)
318  if ( $this->getConfig()->get( 'FooterLinkCacheExpiry' ) <= 0 ) {
319  $titles = array_merge(
320  $titles,
321  array_filter( [
322  $this->footerLinkTitle( 'privacy', 'privacypage' ),
323  $this->footerLinkTitle( 'aboutsite', 'aboutpage' ),
324  $this->footerLinkTitle( 'disclaimers', 'disclaimerpage' ),
325  ] )
326  );
327  }
328 
329  $this->getHookRunner()->onSkinPreloadExistence( $titles, $this );
330 
331  if ( $titles ) {
332  $lb = new LinkBatch( $titles );
333  $lb->setCaller( __METHOD__ );
334  $lb->execute();
335  }
336  }
337 
344  public function getRevisionId() {
345  wfDeprecated( __METHOD__, '1.34' );
346 
347  return $this->getOutput()->getRevisionId();
348  }
349 
356  public function isRevisionCurrent() {
357  wfDeprecated( __METHOD__, '1.34' );
358 
359  return $this->getOutput()->isRevisionCurrent();
360  }
361 
367  public function setRelevantTitle( $t ) {
368  $this->mRelevantTitle = $t;
369  }
370 
381  public function getRelevantTitle() {
382  return $this->mRelevantTitle ?? $this->getTitle();
383  }
384 
390  public function setRelevantUser( $u ) {
391  $this->mRelevantUser = $u;
392  }
393 
402  public function getRelevantUser() {
403  if ( isset( $this->mRelevantUser ) ) {
404  return $this->mRelevantUser;
405  }
406  $title = $this->getRelevantTitle();
407  if ( $title->hasSubjectNamespace( NS_USER ) ) {
408  $rootUser = $title->getRootText();
409  if ( User::isIP( $rootUser ) ) {
410  $this->mRelevantUser = User::newFromName( $rootUser, false );
411  } else {
412  $user = User::newFromName( $rootUser, false );
413 
414  if ( $user ) {
415  $user->load( User::READ_NORMAL );
416 
417  if ( $user->isLoggedIn() ) {
418  $this->mRelevantUser = $user;
419  }
420  }
421  }
422  return $this->mRelevantUser;
423  }
424  return null;
425  }
426 
430  abstract public function outputPage();
431 
438  public static function makeVariablesScript( $data, $nonce = null ) {
439  wfDeprecated( __METHOD__, '1.36' );
440 
441  if ( $data ) {
444  $nonce
445  );
446  }
447  return '';
448  }
449 
456  public function setupSkinUserCss( OutputPage $out ) {
457  wfDeprecated( __METHOD__, '1.32' );
458  }
459 
465  public function getPageClasses( $title ) {
466  $numeric = 'ns-' . $title->getNamespace();
467  $user = $this->getUser();
468 
469  if ( $title->isSpecialPage() ) {
470  $type = 'ns-special';
471  // T25315: provide a class based on the canonical special page name without subpages
472  list( $canonicalName ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
473  resolveAlias( $title->getDBkey() );
474  if ( $canonicalName ) {
475  $type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" );
476  } else {
477  $type .= ' mw-invalidspecialpage';
478  }
479  } else {
480  if ( $title->isTalkPage() ) {
481  $type = 'ns-talk';
482  } else {
483  $type = 'ns-subject';
484  }
485  // T208315: add HTML class when the user can edit the page
486  if ( MediaWikiServices::getInstance()->getPermissionManager()
487  ->quickUserCan( 'edit', $user, $title )
488  ) {
489  $type .= ' mw-editable';
490  }
491  }
492 
493  $name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
494  $root = Sanitizer::escapeClass( 'rootpage-' . $title->getRootTitle()->getPrefixedText() );
495 
496  return "$numeric $type $name $root";
497  }
498 
503  public function getHtmlElementAttributes() {
504  $lang = $this->getLanguage();
505  return [
506  'lang' => $lang->getHtmlCode(),
507  'dir' => $lang->getDir(),
508  'class' => 'client-nojs',
509  ];
510  }
511 
518  protected function getLogo() {
519  wfDeprecated( __METHOD__, '1.36' );
520  return ResourceLoaderSkinModule::getAvailableLogos( $this->getConfig() )[ '1x' ];
521  }
522 
526  public function getCategoryLinks() {
527  $out = $this->getOutput();
528  $allCats = $out->getCategoryLinks();
529  $title = $this->getTitle();
530  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
531 
532  if ( $allCats === [] ) {
533  return '';
534  }
535 
536  $embed = "<li>";
537  $pop = "</li>";
538 
539  $s = '';
540  $colon = $this->msg( 'colon-separator' )->escaped();
541 
542  if ( !empty( $allCats['normal'] ) ) {
543  $t = $embed . implode( $pop . $embed, $allCats['normal'] ) . $pop;
544 
545  $msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) );
546  $linkPage = $this->msg( 'pagecategorieslink' )->inContentLanguage()->text();
547  $pageCategoriesLinkTitle = Title::newFromText( $linkPage );
548  if ( $pageCategoriesLinkTitle ) {
549  $link = $linkRenderer->makeLink( $pageCategoriesLinkTitle, $msg->text() );
550  } else {
551  $link = $msg->escaped();
552  }
553  $s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
554  $link . $colon . '<ul>' . $t . '</ul></div>';
555  }
556 
557  # Hidden categories
558  if ( isset( $allCats['hidden'] ) ) {
559  if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) {
560  $class = ' mw-hidden-cats-user-shown';
561  } elseif ( $title->inNamespace( NS_CATEGORY ) ) {
562  $class = ' mw-hidden-cats-ns-shown';
563  } else {
564  $class = ' mw-hidden-cats-hidden';
565  }
566 
567  $s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
568  $this->msg( 'hidden-categories' )->numParams( count( $allCats['hidden'] ) )->escaped() .
569  $colon . '<ul>' . $embed . implode( $pop . $embed, $allCats['hidden'] ) . $pop . '</ul>' .
570  '</div>';
571  }
572 
573  # optional 'dmoz-like' category browser. Will be shown under the list
574  # of categories an article belong to
575  if ( $this->getConfig()->get( 'UseCategoryBrowser' ) ) {
576  $s .= '<br /><hr />';
577 
578  # get a big array of the parents tree
579  $parenttree = $title->getParentCategoryTree();
580  # Skin object passed by reference cause it can not be
581  # accessed under the method subfunction drawCategoryBrowser
582  $tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree ) );
583  # Clean out bogus first entry and sort them
584  unset( $tempout[0] );
585  asort( $tempout );
586  # Output one per line
587  $s .= implode( "<br />\n", $tempout );
588  }
589 
590  return $s;
591  }
592 
598  protected function drawCategoryBrowser( $tree ) {
599  $return = '';
600  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
601 
602  foreach ( $tree as $element => $parent ) {
603  if ( empty( $parent ) ) {
604  # element start a new list
605  $return .= "\n";
606  } else {
607  # grab the others elements
608  $return .= $this->drawCategoryBrowser( $parent ) . ' &gt; ';
609  }
610 
611  # add our current element to the list
612  $eltitle = Title::newFromText( $element );
613  $return .= $linkRenderer->makeLink( $eltitle, $eltitle->getText() );
614  }
615 
616  return $return;
617  }
618 
622  public function getCategories() {
623  $catlinks = $this->getCategoryLinks();
624  // Check what we're showing
625  $allCats = $this->getOutput()->getCategoryLinks();
626  $showHidden = $this->getUser()->getBoolOption( 'showhiddencats' ) ||
627  $this->getTitle()->inNamespace( NS_CATEGORY );
628 
629  $classes = [ 'catlinks' ];
630  if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
631  $classes[] = 'catlinks-allhidden';
632  }
633 
634  return Html::rawElement(
635  'div',
636  [ 'id' => 'catlinks', 'class' => $classes, 'data-mw' => 'interface' ],
637  $catlinks
638  );
639  }
640 
655  protected function afterContentHook() {
656  $data = '';
657 
658  if ( $this->getHookRunner()->onSkinAfterContent( $data, $this ) ) {
659  // adding just some spaces shouldn't toggle the output
660  // of the whole <div/>, so we use trim() here
661  if ( trim( $data ) != '' ) {
662  // Doing this here instead of in the skins to
663  // ensure that the div has the same ID in all
664  // skins
665  $data = "<div id='mw-data-after-content'>\n" .
666  "\t$data\n" .
667  "</div>\n";
668  }
669  } else {
670  wfDebug( "Hook SkinAfterContent changed output processing." );
671  }
672 
673  return $data;
674  }
675 
683  protected function generateDebugHTML() {
684  wfDeprecated( __METHOD__, '1.35' );
685  return MWDebug::getHTMLDebugLog();
686  }
687 
693  public function bottomScripts() {
694  // TODO and the suckage continues. This function is really just a wrapper around
695  // OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned
696  // up at some point
697  $chunks = [ $this->getOutput()->getBottomScripts() ];
698 
699  // Keep the hook appendage separate to preserve WrappedString objects.
700  // This enables BaseTemplate::getTrail() to merge them where possible.
701  $extraHtml = '';
702  $this->getHookRunner()->onSkinAfterBottomScripts( $this, $extraHtml );
703  if ( $extraHtml !== '' ) {
704  $chunks[] = $extraHtml;
705  }
706  return WrappedString::join( "\n", $chunks );
707  }
708 
716  public function printSource() {
717  $title = $this->getTitle();
718  $oldid = $this->getOutput()->getRevisionId();
719  if ( $oldid ) {
720  $canonicalUrl = $title->getCanonicalURL( 'oldid=' . $oldid );
721  $url = htmlspecialchars( wfExpandIRI( $canonicalUrl ) );
722  } else {
723  // oldid not available for non existing pages
724  $url = htmlspecialchars( wfExpandIRI( $title->getCanonicalURL() ) );
725  }
726 
727  return $this->msg( 'retrievedfrom' )
728  ->rawParams( '<a dir="ltr" href="' . $url . '">' . $url . '</a>' )
729  ->parse();
730  }
731 
735  public function getUndeleteLink() {
736  $action = $this->getRequest()->getVal( 'action', 'view' );
737  $title = $this->getTitle();
738  $user = $this->getUser();
739  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
740  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
741 
742  if ( ( !$title->exists() || $action == 'history' ) &&
743  $permissionManager->quickUserCan( 'deletedhistory', $user, $title )
744  ) {
745  $n = $title->isDeleted();
746 
747  if ( $n ) {
748  if ( $permissionManager->quickUserCan( 'undelete', $user, $title ) ) {
749  $msg = 'thisisdeleted';
750  } else {
751  $msg = 'viewdeleted';
752  }
753 
754  $subtitle = $this->msg( $msg )->rawParams(
755  $linkRenderer->makeKnownLink(
756  SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() ),
757  $this->msg( 'restorelink' )->numParams( $n )->text() )
758  )->escaped();
759 
760  $links = [];
761  // Add link to page logs, unless we're on the history page (which
762  // already has one)
763  if ( $action !== 'history' ) {
764  $links[] = $linkRenderer->makeKnownLink(
765  SpecialPage::getTitleFor( 'Log' ),
766  $this->msg( 'viewpagelogs-lowercase' )->text(),
767  [],
768  [ 'page' => $title->getPrefixedText() ]
769  );
770  }
771 
772  // Allow extensions to add more links
773  $this->getHookRunner()->onUndeletePageToolLinks(
774  $this->getContext(), $linkRenderer, $links );
775 
776  if ( $links ) {
777  $subtitle .= ''
778  . $this->msg( 'word-separator' )->escaped()
779  . $this->msg( 'parentheses' )
780  ->rawParams( $this->getLanguage()->pipeList( $links ) )
781  ->escaped();
782  }
783 
784  return Html::rawElement( 'div', [ 'class' => 'mw-undelete-subtitle' ], $subtitle );
785  }
786  }
787 
788  return '';
789  }
790 
795  public function subPageSubtitle( $out = null ) {
796  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
797  $out = $out ?? $this->getOutput();
798  $title = $out->getTitle();
799  $subpages = '';
800 
801  if ( !$this->getHookRunner()->onSkinSubPageSubtitle( $subpages, $this, $out ) ) {
802  return $subpages;
803  }
804 
805  if (
806  $out->isArticle() && MediaWikiServices::getInstance()->getNamespaceInfo()->
807  hasSubpages( $title->getNamespace() )
808  ) {
809  $ptext = $title->getPrefixedText();
810  if ( strpos( $ptext, '/' ) !== false ) {
811  $links = explode( '/', $ptext );
812  array_pop( $links );
813  $c = 0;
814  $growinglink = '';
815  $display = '';
816  $lang = $this->getLanguage();
817 
818  foreach ( $links as $link ) {
819  $growinglink .= $link;
820  $display .= $link;
821  $linkObj = Title::newFromText( $growinglink );
822 
823  if ( is_object( $linkObj ) && $linkObj->isKnown() ) {
824  $getlink = $linkRenderer->makeKnownLink(
825  $linkObj, $display
826  );
827 
828  $c++;
829 
830  if ( $c > 1 ) {
831  $subpages .= $lang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
832  } else {
833  $subpages .= '&lt; ';
834  }
835 
836  $subpages .= $getlink;
837  $display = '';
838  } else {
839  $display .= '/';
840  }
841  $growinglink .= '/';
842  }
843  }
844  }
845 
846  return $subpages;
847  }
848 
853  protected function getSearchLink() {
854  wfDeprecated( __METHOD__, '1.36' );
855 
856  $searchPage = SpecialPage::getTitleFor( 'Search' );
857  return $searchPage->getLocalURL();
858  }
859 
864  public function getCopyright( $type = 'detect' ) {
865  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
866  if ( $type == 'detect' ) {
867  if ( !$this->getOutput()->isRevisionCurrent()
868  && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled()
869  ) {
870  $type = 'history';
871  } else {
872  $type = 'normal';
873  }
874  }
875 
876  if ( $type == 'history' ) {
877  $msg = 'history_copyright';
878  } else {
879  $msg = 'copyright';
880  }
881 
882  $config = $this->getConfig();
883 
884  if ( $config->get( 'RightsPage' ) ) {
885  $title = Title::newFromText( $config->get( 'RightsPage' ) );
886  $link = $linkRenderer->makeKnownLink(
887  $title, new HtmlArmor( $config->get( 'RightsText' ) )
888  );
889  } elseif ( $config->get( 'RightsUrl' ) ) {
890  $link = Linker::makeExternalLink( $config->get( 'RightsUrl' ), $config->get( 'RightsText' ) );
891  } elseif ( $config->get( 'RightsText' ) ) {
892  $link = $config->get( 'RightsText' );
893  } else {
894  # Give up now
895  return '';
896  }
897 
898  // Allow for site and per-namespace customization of copyright notice.
899  $this->getHookRunner()->onSkinCopyrightFooter( $this->getTitle(), $type, $msg, $link );
900 
901  return $this->msg( $msg )->rawParams( $link )->text();
902  }
903 
907  protected function getCopyrightIcon() {
908  $out = '';
909  $config = $this->getConfig();
910 
911  $footerIcons = $config->get( 'FooterIcons' );
912  if ( $footerIcons['copyright']['copyright'] ) {
913  $out = $footerIcons['copyright']['copyright'];
914  } elseif ( $config->get( 'RightsIcon' ) ) {
915  $icon = htmlspecialchars( $config->get( 'RightsIcon' ) );
916  $url = $config->get( 'RightsUrl' );
917 
918  if ( $url ) {
919  $out .= '<a href="' . htmlspecialchars( $url ) . '">';
920  }
921 
922  $text = htmlspecialchars( $config->get( 'RightsText' ) );
923  $out .= "<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
924 
925  if ( $url ) {
926  $out .= '</a>';
927  }
928  }
929 
930  return $out;
931  }
932 
937  protected function getPoweredBy() {
938  $resourceBasePath = $this->getConfig()->get( 'ResourceBasePath' );
939  $url1 = htmlspecialchars(
940  "$resourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
941  );
942  $url1_5 = htmlspecialchars(
943  "$resourceBasePath/resources/assets/poweredby_mediawiki_132x47.png"
944  );
945  $url2 = htmlspecialchars(
946  "$resourceBasePath/resources/assets/poweredby_mediawiki_176x62.png"
947  );
948  $text = '<a href="https://www.mediawiki.org/"><img src="' . $url1
949  . '" srcset="' . $url1_5 . ' 1.5x, ' . $url2 . ' 2x" '
950  . 'height="31" width="88" alt="Powered by MediaWiki" loading="lazy" /></a>';
951  $this->getHookRunner()->onSkinGetPoweredBy( $text, $this );
952  return $text;
953  }
954 
960  protected function lastModified() {
961  $timestamp = $this->getOutput()->getRevisionTimestamp();
962  $user = $this->getUser();
963  $language = $this->getLanguage();
964 
965  # No cached timestamp, load it from the database
966  if ( $timestamp === null ) {
967  $revId = $this->getOutput()->getRevisionId();
968  if ( $revId !== null ) {
969  $timestamp = MediaWikiServices::getInstance()
970  ->getRevisionLookup()
971  ->getTimestampFromId( $revId );
972  }
973  }
974 
975  if ( $timestamp ) {
976  $d = $language->userDate( $timestamp, $user );
977  $t = $language->userTime( $timestamp, $user );
978  $s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->parse();
979  } else {
980  $s = '';
981  }
982 
983  if ( MediaWikiServices::getInstance()->getDBLoadBalancer()->getLaggedReplicaMode() ) {
984  $s .= ' <strong>' . $this->msg( 'laggedreplicamode' )->parse() . '</strong>';
985  }
986 
987  return $s;
988  }
989 
994  public function logoText( $align = '' ) {
995  if ( $align != '' ) {
996  $a = " style='float: {$align};'";
997  } else {
998  $a = '';
999  }
1000 
1001  $mp = $this->msg( 'mainpage' )->escaped();
1002  $mptitle = Title::newMainPage();
1003  $url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) : '' );
1004 
1005  $logourl = ResourceLoaderSkinModule::getAvailableLogos( $this->getConfig() )[ '1x' ];
1006  return "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
1007  }
1008 
1017  public function makeFooterIcon( $icon, $withImage = 'withImage' ) {
1018  if ( is_string( $icon ) ) {
1019  $html = $icon;
1020  } else { // Assuming array
1021  $url = $icon["url"] ?? null;
1022  unset( $icon["url"] );
1023  if ( isset( $icon["src"] ) && $withImage === 'withImage' ) {
1024  // Lazy-load footer icons, since they're not part of the printed view.
1025  $icon["loading"] = 'lazy';
1026  // do this the lazy way, just pass icon data as an attribute array
1027  $html = Html::element( 'img', $icon );
1028  } else {
1029  $html = htmlspecialchars( $icon["alt"] );
1030  }
1031  if ( $url ) {
1032  $html = Html::rawElement( 'a',
1033  [ "href" => $url, "target" => $this->getConfig()->get( 'ExternalLinkTarget' ) ],
1034  $html );
1035  }
1036  }
1037  return $html;
1038  }
1039 
1046  public function mainPageLink() {
1047  wfDeprecated( __METHOD__, '1.36' );
1048 
1049  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1050  $s = $linkRenderer->makeKnownLink(
1052  $this->msg( 'mainpage' )->text()
1053  );
1054 
1055  return $s;
1056  }
1057 
1075  public function footerLink( $desc, $page ) {
1076  $title = $this->footerLinkTitle( $desc, $page );
1077 
1078  if ( !$title ) {
1079  return '';
1080  }
1081 
1082  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1083  return $linkRenderer->makeKnownLink(
1084  $title,
1085  $this->msg( $desc )->text()
1086  );
1087  }
1088 
1094  private function footerLinkTitle( $desc, $page ) {
1095  // If the link description has been disabled in the default language,
1096  if ( $this->msg( $desc )->inContentLanguage()->isDisabled() ) {
1097  // then it is disabled, for all languages.
1098  return null;
1099  }
1100  // Otherwise, we display the link for the user, described in their
1101  // language (which may or may not be the same as the default language),
1102  // but we make the link target be the one site-wide page.
1103  $title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() );
1104 
1105  return $title ?: null;
1106  }
1107 
1114  public function getSiteFooterLinks() {
1115  $callback = function () {
1116  return [
1117  'privacy' => $this->footerLink( 'privacy', 'privacypage' ),
1118  'about' => $this->footerLink( 'aboutsite', 'aboutpage' ),
1119  'disclaimer' => $this->footerLink( 'disclaimers', 'disclaimerpage' )
1120  ];
1121  };
1122 
1123  $services = MediaWikiServices::getInstance();
1124  $msgCache = $services->getMessageCache();
1125  $wanCache = $services->getMainWANObjectCache();
1126  $config = $this->getConfig();
1127 
1128  return ( $config->get( 'FooterLinkCacheExpiry' ) > 0 )
1129  ? $wanCache->getWithSetCallback(
1130  $wanCache->makeKey( 'footer-links' ),
1131  $config->get( 'FooterLinkCacheExpiry' ),
1132  $callback,
1133  [
1134  'checkKeys' => [
1135  // Unless there is both no exact $code override nor an i18n definition
1136  // in the software, the only MediaWiki page to check is for $code.
1137  $msgCache->getCheckKey( $this->getLanguage()->getCode() )
1138  ],
1139  'lockTSE' => 30
1140  ]
1141  )
1142  : $callback();
1143  }
1144 
1151  public function privacyLink() {
1152  wfDeprecated( __METHOD__, '1.36' );
1153  return $this->footerLink( 'privacy', 'privacypage' );
1154  }
1155 
1162  public function aboutLink() {
1163  wfDeprecated( __METHOD__, '1.36' );
1164  return $this->footerLink( 'aboutsite', 'aboutpage' );
1165  }
1166 
1173  public function disclaimerLink() {
1174  wfDeprecated( __METHOD__, '1.36' );
1175  return $this->footerLink( 'disclaimers', 'disclaimerpage' );
1176  }
1177 
1185  public function editUrlOptions() {
1186  $options = [ 'action' => 'edit' ];
1187  $out = $this->getOutput();
1188 
1189  if ( !$out->isRevisionCurrent() ) {
1190  $options['oldid'] = intval( $out->getRevisionId() );
1191  }
1192 
1193  return $options;
1194  }
1195 
1200  public function showEmailUser( $id ) {
1201  if ( $id instanceof User ) {
1202  $targetUser = $id;
1203  } else {
1204  $targetUser = User::newFromId( $id );
1205  }
1206 
1207  # The sending user must have a confirmed email address and the receiving
1208  # user must accept emails from the sender.
1209  return $this->getUser()->canSendEmail()
1210  && SpecialEmailUser::validateTarget( $targetUser, $this->getUser() ) === '';
1211  }
1212 
1224  public function getSkinStylePath( $name ) {
1225  if ( $this->stylename === null ) {
1226  $class = static::class;
1227  throw new MWException( "$class::\$stylename must be set to use getSkinStylePath()" );
1228  }
1229 
1230  return $this->getConfig()->get( 'StylePath' ) . "/{$this->stylename}/$name";
1231  }
1232 
1233  /* these are used extensively in SkinTemplate, but also some other places */
1234 
1239  public static function makeMainPageUrl( $urlaction = '' ) {
1241  self::checkTitle( $title, '' );
1242 
1243  return $title->getLinkURL( $urlaction );
1244  }
1245 
1257  public static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) {
1259  if ( $proto === null ) {
1260  return $title->getLocalURL( $urlaction );
1261  } else {
1262  return $title->getFullURL( $urlaction, false, $proto );
1263  }
1264  }
1265 
1272  public static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
1273  $title = SpecialPage::getSafeTitleFor( $name, $subpage );
1274  return $title->getLocalURL( $urlaction );
1275  }
1276 
1283  public static function makeUrl( $name, $urlaction = '' ) {
1284  wfDeprecated( __METHOD__, '1.36' );
1285 
1286  $title = Title::newFromText( $name );
1287  self::checkTitle( $title, $name );
1288  return $title->getLocalURL( $urlaction );
1289  }
1290 
1297  public static function makeInternalOrExternalUrl( $name ) {
1298  if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $name ) ) {
1299  return $name;
1300  } else {
1301  $title = Title::newFromText( $name );
1302  self::checkTitle( $title, $name );
1303  return $title->getLocalURL();
1304  }
1305  }
1306 
1315  public static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
1316  wfDeprecated( __METHOD__, '1.35' );
1317  $title = Title::makeTitleSafe( $namespace, $name );
1318  self::checkTitle( $title, $name );
1319 
1320  return $title->getLocalURL( $urlaction );
1321  }
1322 
1329  protected static function makeUrlDetails( $name, $urlaction = '' ) {
1330  $title = Title::newFromText( $name );
1331  self::checkTitle( $title, $name );
1332 
1333  return [
1334  'href' => $title->getLocalURL( $urlaction ),
1335  'exists' => $title->isKnown(),
1336  ];
1337  }
1338 
1345  protected static function makeKnownUrlDetails( $name, $urlaction = '' ) {
1346  $title = Title::newFromText( $name );
1347  self::checkTitle( $title, $name );
1348 
1349  return [
1350  'href' => $title->getLocalURL( $urlaction ),
1351  'exists' => true
1352  ];
1353  }
1354 
1361  public static function checkTitle( &$title, $name ) {
1362  if ( !is_object( $title ) ) {
1363  $title = Title::newFromText( $name );
1364  if ( !is_object( $title ) ) {
1365  $title = Title::newFromText( '--error: link target missing--' );
1366  }
1367  }
1368  }
1369 
1378  public function mapInterwikiToLanguage( $code ) {
1379  $map = $this->getConfig()->get( 'InterlanguageLinkCodeMap' );
1380  return $map[ $code ] ?? $code;
1381  }
1382 
1391  public function getLanguages() {
1392  if ( $this->getConfig()->get( 'HideInterlanguageLinks' ) ) {
1393  return [];
1394  }
1395  $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1396 
1397  $userLang = $this->getLanguage();
1398  $languageLinks = [];
1399  $langNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
1400 
1401  foreach ( $this->getOutput()->getLanguageLinks() as $languageLinkText ) {
1402  $class = 'interlanguage-link interwiki-' . explode( ':', $languageLinkText, 2 )[0];
1403 
1404  $languageLinkTitle = Title::newFromText( $languageLinkText );
1405  if ( !$languageLinkTitle ) {
1406  continue;
1407  }
1408 
1409  $ilInterwikiCode = $this->mapInterwikiToLanguage( $languageLinkTitle->getInterwiki() );
1410 
1411  $ilLangName = $langNameUtils->getLanguageName( $ilInterwikiCode );
1412 
1413  if ( strval( $ilLangName ) === '' ) {
1414  $ilDisplayTextMsg = $this->msg( "interlanguage-link-$ilInterwikiCode" );
1415  if ( !$ilDisplayTextMsg->isDisabled() ) {
1416  // Use custom MW message for the display text
1417  $ilLangName = $ilDisplayTextMsg->text();
1418  } else {
1419  // Last resort: fallback to the language link target
1420  $ilLangName = $languageLinkText;
1421  }
1422  } else {
1423  // Use the language autonym as display text
1424  $ilLangName = $this->getLanguage()->ucfirst( $ilLangName );
1425  }
1426 
1427  // CLDR extension or similar is required to localize the language name;
1428  // otherwise we'll end up with the autonym again.
1429  $ilLangLocalName = $langNameUtils->getLanguageName(
1430  $ilInterwikiCode,
1431  $userLang->getCode()
1432  );
1433 
1434  $languageLinkTitleText = $languageLinkTitle->getText();
1435  if ( $ilLangLocalName === '' ) {
1436  $ilFriendlySiteName = $this->msg( "interlanguage-link-sitename-$ilInterwikiCode" );
1437  if ( !$ilFriendlySiteName->isDisabled() ) {
1438  if ( $languageLinkTitleText === '' ) {
1439  $ilTitle = $this->msg(
1440  'interlanguage-link-title-nonlangonly',
1441  $ilFriendlySiteName->text()
1442  )->text();
1443  } else {
1444  $ilTitle = $this->msg(
1445  'interlanguage-link-title-nonlang',
1446  $languageLinkTitleText,
1447  $ilFriendlySiteName->text()
1448  )->text();
1449  }
1450  } else {
1451  // we have nothing friendly to put in the title, so fall back to
1452  // displaying the interlanguage link itself in the title text
1453  // (similar to what is done in page content)
1454  $ilTitle = $languageLinkTitle->getInterwiki() .
1455  ":$languageLinkTitleText";
1456  }
1457  } elseif ( $languageLinkTitleText === '' ) {
1458  $ilTitle = $this->msg(
1459  'interlanguage-link-title-langonly',
1460  $ilLangLocalName
1461  )->text();
1462  } else {
1463  $ilTitle = $this->msg(
1464  'interlanguage-link-title',
1465  $languageLinkTitleText,
1466  $ilLangLocalName
1467  )->text();
1468  }
1469 
1470  $ilInterwikiCodeBCP47 = LanguageCode::bcp47( $ilInterwikiCode );
1471  $languageLink = [
1472  'href' => $languageLinkTitle->getFullURL(),
1473  'text' => $ilLangName,
1474  'title' => $ilTitle,
1475  'class' => $class,
1476  'link-class' => 'interlanguage-link-target',
1477  'lang' => $ilInterwikiCodeBCP47,
1478  'hreflang' => $ilInterwikiCodeBCP47,
1479  ];
1480  $hookContainer->run(
1481  'SkinTemplateGetLanguageLink',
1482  [ &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() ],
1483  []
1484  );
1485  $languageLinks[] = $languageLink;
1486  }
1487 
1488  return $languageLinks;
1489  }
1490 
1497  protected function buildNavUrls() {
1498  $out = $this->getOutput();
1499  $request = $this->getRequest();
1500  $title = $this->getTitle();
1501  $thispage = $title->getPrefixedDBkey();
1502  $uploadNavigationUrl = $this->getConfig()->get( 'UploadNavigationUrl' );
1503 
1504  $nav_urls = [];
1505  $nav_urls['mainpage'] = [ 'href' => self::makeMainPageUrl() ];
1506  if ( $uploadNavigationUrl ) {
1507  $nav_urls['upload'] = [ 'href' => $uploadNavigationUrl ];
1508  } elseif ( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) {
1509  $nav_urls['upload'] = [ 'href' => self::makeSpecialUrl( 'Upload' ) ];
1510  } else {
1511  $nav_urls['upload'] = false;
1512  }
1513  $nav_urls['specialpages'] = [ 'href' => self::makeSpecialUrl( 'Specialpages' ) ];
1514 
1515  $nav_urls['print'] = false;
1516  $nav_urls['permalink'] = false;
1517  $nav_urls['info'] = false;
1518  $nav_urls['whatlinkshere'] = false;
1519  $nav_urls['recentchangeslinked'] = false;
1520  $nav_urls['contributions'] = false;
1521  $nav_urls['log'] = false;
1522  $nav_urls['blockip'] = false;
1523  $nav_urls['mute'] = false;
1524  $nav_urls['emailuser'] = false;
1525  $nav_urls['userrights'] = false;
1526 
1527  // A print stylesheet is attached to all pages, but nobody ever
1528  // figures that out. :) Add a link...
1529  if ( !$out->isPrintable() && ( $out->isArticle() || $title->isSpecialPage() ) ) {
1530  $nav_urls['print'] = [
1531  'text' => $this->msg( 'printableversion' )->text(),
1532  'href' => 'javascript:print();'
1533  ];
1534  }
1535 
1536  if ( $out->isArticle() ) {
1537  // Also add a "permalink" while we're at it
1538  $revid = $out->getRevisionId();
1539  if ( $revid ) {
1540  $nav_urls['permalink'] = [
1541  'text' => $this->msg( 'permalink' )->text(),
1542  'href' => $title->getLocalURL( "oldid=$revid" )
1543  ];
1544  }
1545  }
1546 
1547  if ( $out->isArticleRelated() ) {
1548  $nav_urls['whatlinkshere'] = [
1549  'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $thispage )->getLocalURL()
1550  ];
1551 
1552  $nav_urls['info'] = [
1553  'text' => $this->msg( 'pageinfo-toolboxlink' )->text(),
1554  'href' => $title->getLocalURL( "action=info" )
1555  ];
1556 
1557  if ( $title->exists() || $title->inNamespace( NS_CATEGORY ) ) {
1558  $nav_urls['recentchangeslinked'] = [
1559  'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $thispage )->getLocalURL()
1560  ];
1561  }
1562  }
1563 
1564  $user = $this->getRelevantUser();
1565  if ( $user ) {
1566  $rootUser = $user->getName();
1567 
1568  $nav_urls['contributions'] = [
1569  'text' => $this->msg( 'contributions', $rootUser )->text(),
1570  'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser ),
1571  'tooltip-params' => [ $rootUser ],
1572  ];
1573 
1574  $nav_urls['log'] = [
1575  'href' => self::makeSpecialUrlSubpage( 'Log', $rootUser )
1576  ];
1577 
1578  if (
1579  MediaWikiServices::getInstance()
1581  ->userHasRight( $this->getUser(), 'block' )
1582  ) {
1583  $nav_urls['blockip'] = [
1584  'text' => $this->msg( 'blockip', $rootUser )->text(),
1585  'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser )
1586  ];
1587  }
1588 
1589  if ( $this->showEmailUser( $user ) ) {
1590  $nav_urls['emailuser'] = [
1591  'text' => $this->msg( 'tool-link-emailuser', $rootUser )->text(),
1592  'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser ),
1593  'tooltip-params' => [ $rootUser ],
1594  ];
1595  }
1596 
1597  if ( !$user->isAnon() ) {
1598  if ( $this->getUser()->isRegistered() && $this->getConfig()->get( 'EnableSpecialMute' ) ) {
1599  $nav_urls['mute'] = [
1600  'text' => $this->msg( 'mute-preferences' )->text(),
1601  'href' => self::makeSpecialUrlSubpage( 'Mute', $rootUser )
1602  ];
1603  }
1604 
1605  $sur = new UserrightsPage;
1606  $sur->setContext( $this->getContext() );
1607  $canChange = $sur->userCanChangeRights( $user );
1608  $nav_urls['userrights'] = [
1609  'text' => $this->msg(
1610  $canChange ? 'tool-link-userrights' : 'tool-link-userrights-readonly',
1611  $rootUser
1612  )->text(),
1613  'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
1614  ];
1615  }
1616  }
1617 
1618  return $nav_urls;
1619  }
1620 
1626  final protected function buildFeedUrls() {
1627  $feeds = [];
1628  $out = $this->getOutput();
1629  if ( $out->isSyndicated() ) {
1630  foreach ( $out->getSyndicationLinks() as $format => $link ) {
1631  $feeds[$format] = [
1632  // Messages: feed-atom, feed-rss
1633  'text' => $this->msg( "feed-$format" )->text(),
1634  'href' => $link
1635  ];
1636  }
1637  }
1638  return $feeds;
1639  }
1640 
1665  public function buildSidebar() {
1666  $services = MediaWikiServices::getInstance();
1667  $callback = function ( $old = null, &$ttl = null ) {
1668  $bar = [];
1669  $this->addToSidebar( $bar, 'sidebar' );
1670  $this->getHookRunner()->onSkinBuildSidebar( $this, $bar );
1671  $msgCache = MediaWikiServices::getInstance()->getMessageCache();
1672  if ( $msgCache->isDisabled() ) {
1673  $ttl = WANObjectCache::TTL_UNCACHEABLE; // bug T133069
1674  }
1675 
1676  return $bar;
1677  };
1678 
1679  $msgCache = $services->getMessageCache();
1680  $wanCache = $services->getMainWANObjectCache();
1681  $config = $this->getConfig();
1682  $languageCode = $this->getLanguage()->getCode();
1683 
1684  $sidebar = $config->get( 'EnableSidebarCache' )
1685  ? $wanCache->getWithSetCallback(
1686  $wanCache->makeKey( 'sidebar', $languageCode ),
1687  $config->get( 'SidebarCacheExpiry' ),
1688  $callback,
1689  [
1690  'checkKeys' => [
1691  // Unless there is both no exact $code override nor an i18n definition
1692  // in the software, the only MediaWiki page to check is for $code.
1693  $msgCache->getCheckKey( $languageCode )
1694  ],
1695  'lockTSE' => 30
1696  ]
1697  )
1698  : $callback();
1699 
1700  $sidebar['TOOLBOX'] = $this->makeToolbox(
1701  $this->buildNavUrls(),
1702  $this->buildFeedUrls()
1703  );
1704  $sidebar['LANGUAGES'] = $this->getLanguages();
1705  // Apply post-processing to the cached value
1706  $this->getHookRunner()->onSidebarBeforeOutput( $this, $sidebar );
1707 
1708  return $sidebar;
1709  }
1710 
1720  public function addToSidebar( &$bar, $message ) {
1721  $this->addToSidebarPlain( $bar, $this->msg( $message )->inContentLanguage()->plain() );
1722  }
1723 
1731  public function addToSidebarPlain( &$bar, $text ) {
1732  $lines = explode( "\n", $text );
1733 
1734  $heading = '';
1735  $config = $this->getConfig();
1736  $messageTitle = $config->get( 'EnableSidebarCache' )
1737  ? Title::newMainPage() : $this->getTitle();
1738  $messageCache = MediaWikiServices::getInstance()->getMessageCache();
1739 
1740  foreach ( $lines as $line ) {
1741  if ( strpos( $line, '*' ) !== 0 ) {
1742  continue;
1743  }
1744  $line = rtrim( $line, "\r" ); // for Windows compat
1745 
1746  if ( strpos( $line, '**' ) !== 0 ) {
1747  $heading = trim( $line, '* ' );
1748  if ( !array_key_exists( $heading, $bar ) ) {
1749  $bar[$heading] = [];
1750  }
1751  } else {
1752  $line = trim( $line, '* ' );
1753 
1754  if ( strpos( $line, '|' ) !== false ) { // sanity check
1755  $line = $messageCache->transform( $line, false, null, $messageTitle );
1756  $line = array_map( 'trim', explode( '|', $line, 2 ) );
1757  if ( count( $line ) !== 2 ) {
1758  // Second sanity check, could be hit by people doing
1759  // funky stuff with parserfuncs... (T35321)
1760  continue;
1761  }
1762 
1763  $extraAttribs = [];
1764 
1765  $msgLink = $this->msg( $line[0] )->title( $messageTitle )->inContentLanguage();
1766  if ( $msgLink->exists() ) {
1767  $link = $msgLink->text();
1768  if ( $link == '-' ) {
1769  continue;
1770  }
1771  } else {
1772  $link = $line[0];
1773  }
1774  $msgText = $this->msg( $line[1] )->title( $messageTitle );
1775  if ( $msgText->exists() ) {
1776  $text = $msgText->text();
1777  } else {
1778  $text = $line[1];
1779  }
1780 
1781  if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $link ) ) {
1782  $href = $link;
1783 
1784  // Parser::getExternalLinkAttribs won't work here because of the Namespace things
1785  if ( $config->get( 'NoFollowLinks' ) &&
1786  !wfMatchesDomainList( $href, $config->get( 'NoFollowDomainExceptions' ) )
1787  ) {
1788  $extraAttribs['rel'] = 'nofollow';
1789  }
1790 
1791  if ( $config->get( 'ExternalLinkTarget' ) ) {
1792  $extraAttribs['target'] = $config->get( 'ExternalLinkTarget' );
1793  }
1794  } else {
1795  $title = Title::newFromText( $link );
1796 
1797  if ( $title ) {
1798  $title = $title->fixSpecialName();
1799  $href = $title->getLinkURL();
1800  } else {
1801  $href = 'INVALID-TITLE';
1802  }
1803  }
1804 
1805  $bar[$heading][] = array_merge( [
1806  'text' => $text,
1807  'href' => $href,
1808  'id' => Sanitizer::escapeIdForAttribute( 'n-' . strtr( $line[1], ' ', '-' ) ),
1809  'active' => false,
1810  ], $extraAttribs );
1811  } else {
1812  continue;
1813  }
1814  }
1815  }
1816 
1817  return $bar;
1818  }
1819 
1825  public function getNewtalks() {
1826  $newMessagesAlert = '';
1827  $user = $this->getUser();
1828  $services = MediaWikiServices::getInstance();
1829  $linkRenderer = $services->getLinkRenderer();
1830  $userHasNewMessages = $services->getTalkPageNotificationManager()
1831  ->userHasNewMessages( $user );
1832  $timestamp = $services->getTalkPageNotificationManager()
1833  ->getLatestSeenMessageTimestamp( $user );
1834  $newtalks = !$userHasNewMessages ? [] : [
1835  [
1836  // TODO: Deprecate adding wiki and link to array and redesign GetNewMessagesAlert hook
1837  'wiki' => WikiMap::getCurrentWikiId(),
1838  'link' => $user->getTalkPage()->getLocalURL(),
1839  'rev' => $timestamp ? $services->getRevisionLookup()
1840  ->getRevisionByTimestamp( $user->getTalkPage(), $timestamp ) : null
1841  ]
1842  ];
1843  $out = $this->getOutput();
1844 
1845  // Allow extensions to disable or modify the new messages alert
1846  if ( !$this->getHookRunner()->onGetNewMessagesAlert(
1847  $newMessagesAlert, $newtalks, $user, $out )
1848  ) {
1849  return '';
1850  }
1851  if ( $newMessagesAlert ) {
1852  return $newMessagesAlert;
1853  }
1854 
1855  if ( $newtalks !== [] ) {
1856  $uTalkTitle = $user->getTalkPage();
1857  $lastSeenRev = $newtalks[0]['rev'];
1858  $numAuthors = 0;
1859  if ( $lastSeenRev !== null ) {
1860  $plural = true; // Default if we have a last seen revision: if unknown, use plural
1861  $revStore = $services->getRevisionStore();
1862  $latestRev = $revStore->getRevisionByTitle(
1863  $uTalkTitle,
1864  0,
1865  RevisionLookup::READ_NORMAL
1866  );
1867  if ( $latestRev !== null ) {
1868  // Singular if only 1 unseen revision, plural if several unseen revisions.
1869  $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
1870  $numAuthors = $revStore->countAuthorsBetween(
1871  $uTalkTitle->getArticleID(),
1872  $lastSeenRev,
1873  $latestRev,
1874  null,
1875  10,
1876  RevisionStore::INCLUDE_NEW
1877  );
1878  }
1879  } else {
1880  // Singular if no revision -> diff link will show latest change only in any case
1881  $plural = false;
1882  }
1883  $plural = $plural ? 999 : 1;
1884  // 999 signifies "more than one revision". We don't know how many, and even if we did,
1885  // the number of revisions or authors is not necessarily the same as the number of
1886  // "messages".
1887  $newMessagesLink = $linkRenderer->makeKnownLink(
1888  $uTalkTitle,
1889  $this->msg( 'newmessageslinkplural' )->params( $plural )->text(),
1890  [],
1891  $uTalkTitle->isRedirect() ? [ 'redirect' => 'no' ] : []
1892  );
1893 
1894  $newMessagesDiffLink = $linkRenderer->makeKnownLink(
1895  $uTalkTitle,
1896  $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->text(),
1897  [],
1898  $lastSeenRev !== null
1899  ? [ 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' ]
1900  : [ 'diff' => 'cur' ]
1901  );
1902 
1903  if ( $numAuthors >= 1 && $numAuthors <= 10 ) {
1904  $newMessagesAlert = $this->msg(
1905  'youhavenewmessagesfromusers',
1906  $newMessagesLink,
1907  $newMessagesDiffLink
1908  )->numParams( $numAuthors, $plural );
1909  } else {
1910  // $numAuthors === 11 signifies "11 or more" ("more than 10")
1911  $newMessagesAlert = $this->msg(
1912  $numAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
1913  $newMessagesLink,
1914  $newMessagesDiffLink
1915  )->numParams( $plural );
1916  }
1917  $newMessagesAlert = $newMessagesAlert->text();
1918  // Disable CDN cache
1919  $out->setCdnMaxage( 0 );
1920  }
1921 
1922  return $newMessagesAlert;
1923  }
1924 
1932  private function getCachedNotice( $name ) {
1933  $config = $this->getConfig();
1934 
1935  if ( $name === 'default' ) {
1936  // special case
1937  $notice = $config->get( 'SiteNotice' );
1938  if ( empty( $notice ) ) {
1939  return false;
1940  }
1941  } else {
1942  $msg = $this->msg( $name )->inContentLanguage();
1943  if ( $msg->isBlank() ) {
1944  return '';
1945  } elseif ( $msg->isDisabled() ) {
1946  return false;
1947  }
1948  $notice = $msg->plain();
1949  }
1950 
1951  $services = MediaWikiServices::getInstance();
1952  $cache = $services->getMainWANObjectCache();
1953  $parsed = $cache->getWithSetCallback(
1954  // Use the extra hash appender to let eg SSL variants separately cache
1955  // Key is verified with md5 hash of unparsed wikitext
1956  $cache->makeKey( $name, $config->get( 'RenderHashAppend' ), md5( $notice ) ),
1957  // TTL in seconds
1958  600,
1959  function () use ( $notice ) {
1960  return $this->getOutput()->parseAsInterface( $notice );
1961  }
1962  );
1963 
1964  $contLang = $services->getContentLanguage();
1965  return Html::rawElement(
1966  'div',
1967  [
1968  'id' => 'localNotice',
1969  'lang' => $contLang->getHtmlCode(),
1970  'dir' => $contLang->getDir()
1971  ],
1972  $parsed
1973  );
1974  }
1975 
1981  public function getSiteNotice() {
1982  $siteNotice = '';
1983 
1984  if ( $this->getHookRunner()->onSiteNoticeBefore( $siteNotice, $this ) ) {
1985  if ( $this->getUser()->isLoggedIn() ) {
1986  $siteNotice = $this->getCachedNotice( 'sitenotice' );
1987  } else {
1988  $anonNotice = $this->getCachedNotice( 'anonnotice' );
1989  if ( $anonNotice === false ) {
1990  $siteNotice = $this->getCachedNotice( 'sitenotice' );
1991  } else {
1992  $siteNotice = $anonNotice;
1993  }
1994  }
1995  if ( $siteNotice === false ) {
1996  $siteNotice = $this->getCachedNotice( 'default' ) ?: '';
1997  }
1998  }
1999 
2000  $this->getHookRunner()->onSiteNoticeAfter( $siteNotice, $this );
2001  return $siteNotice;
2002  }
2003 
2017  public function doEditSectionLink( Title $nt, $section, $tooltip, Language $lang ) {
2018  // HTML generated here should probably have userlangattributes
2019  // added to it for LTR text on RTL pages
2020 
2021  $attribs = [];
2022  if ( $tooltip !== null ) {
2023  $attribs['title'] = $this->msg( 'editsectionhint' )->rawParams( $tooltip )
2024  ->inLanguage( $lang )->text();
2025  }
2026 
2027  $links = [
2028  'editsection' => [
2029  'text' => $this->msg( 'editsection' )->inLanguage( $lang )->text(),
2030  'targetTitle' => $nt,
2031  'attribs' => $attribs,
2032  'query' => [ 'action' => 'edit', 'section' => $section ]
2033  ]
2034  ];
2035 
2036  $this->getHookRunner()->onSkinEditSectionLinks( $this, $nt, $section, $tooltip, $links, $lang );
2037 
2038  $result = '<span class="mw-editsection"><span class="mw-editsection-bracket">[</span>';
2039 
2040  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2041  $linksHtml = [];
2042  foreach ( $links as $k => $linkDetails ) {
2043  $linksHtml[] = $linkRenderer->makeKnownLink(
2044  $linkDetails['targetTitle'],
2045  $linkDetails['text'],
2046  $linkDetails['attribs'],
2047  $linkDetails['query']
2048  );
2049  }
2050 
2051  $result .= implode(
2052  '<span class="mw-editsection-divider">'
2053  . $this->msg( 'pipe-separator' )->inLanguage( $lang )->escaped()
2054  . '</span>',
2055  $linksHtml
2056  );
2057 
2058  $result .= '<span class="mw-editsection-bracket">]</span></span>';
2059  return $result;
2060  }
2061 
2071  public function makeToolbox( $navUrls, $feedUrls ) {
2072  $toolbox = [];
2073  if ( $navUrls['whatlinkshere'] ?? null ) {
2074  $toolbox['whatlinkshere'] = $navUrls['whatlinkshere'];
2075  $toolbox['whatlinkshere']['id'] = 't-whatlinkshere';
2076  }
2077  if ( $navUrls['recentchangeslinked'] ?? null ) {
2078  $toolbox['recentchangeslinked'] = $navUrls['recentchangeslinked'];
2079  $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
2080  $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
2081  $toolbox['recentchangeslinked']['rel'] = 'nofollow';
2082  }
2083  if ( $feedUrls ?? null ) {
2084  $toolbox['feeds']['id'] = 'feedlinks';
2085  $toolbox['feeds']['links'] = [];
2086  foreach ( $feedUrls as $key => $feed ) {
2087  $toolbox['feeds']['links'][$key] = $feed;
2088  $toolbox['feeds']['links'][$key]['id'] = "feed-$key";
2089  $toolbox['feeds']['links'][$key]['rel'] = 'alternate';
2090  $toolbox['feeds']['links'][$key]['type'] = "application/{$key}+xml";
2091  $toolbox['feeds']['links'][$key]['class'] = 'feedlink';
2092  }
2093  }
2094  foreach ( [ 'contributions', 'log', 'blockip', 'emailuser', 'mute',
2095  'userrights', 'upload', 'specialpages' ] as $special
2096  ) {
2097  if ( $navUrls[$special] ?? null ) {
2098  $toolbox[$special] = $navUrls[$special];
2099  $toolbox[$special]['id'] = "t-$special";
2100  }
2101  }
2102  if ( $navUrls['print'] ?? null ) {
2103  $toolbox['print'] = $navUrls['print'];
2104  $toolbox['print']['id'] = 't-print';
2105  $toolbox['print']['rel'] = 'alternate';
2106  $toolbox['print']['msg'] = 'printableversion';
2107  }
2108  if ( $navUrls['permalink'] ?? null ) {
2109  $toolbox['permalink'] = $navUrls['permalink'];
2110  $toolbox['permalink']['id'] = 't-permalink';
2111  }
2112  if ( $navUrls['info'] ?? null ) {
2113  $toolbox['info'] = $navUrls['info'];
2114  $toolbox['info']['id'] = 't-info';
2115  }
2116 
2117  return $toolbox;
2118  }
2119 
2137  final public function getIndicatorsHTML( $indicators ) {
2138  wfDeprecated( __METHOD__, '1.36' );
2139 
2140  $out = "<div class=\"mw-indicators mw-body-content\">\n";
2141  foreach ( $this->getIndicatorsData( $indicators ) as $indicatorData ) {
2142  $out .= Html::rawElement(
2143  'div',
2144  [
2145  'id' => $indicatorData['id'],
2146  'class' => $indicatorData['class']
2147  ],
2148  $indicatorData['html']
2149  ) . "\n";
2150  }
2151  $out .= "</div>\n";
2152  return $out;
2153  }
2154 
2161  protected function getIndicatorsData( $indicators ) {
2162  $indicatorData = [];
2163  foreach ( $indicators as $id => $content ) {
2164  $indicatorData[] = [
2165  'id' => Sanitizer::escapeIdForAttribute( "mw-indicator-$id" ),
2166  'class' => 'mw-indicator',
2167  'html' => $content,
2168  ];
2169  }
2170  return $indicatorData;
2171  }
2172 
2185  final public function getPersonalToolsForMakeListItem( $urls ) {
2186  $personal_tools = [];
2187  foreach ( $urls as $key => $plink ) {
2188  # The class on a personal_urls item is meant to go on the <a> instead
2189  # of the <li> so we have to use a single item "links" array instead
2190  # of using most of the personal_url's keys directly.
2191  $ptool = [
2192  'links' => [
2193  [ 'single-id' => "pt-$key" ],
2194  ],
2195  'id' => "pt-$key",
2196  ];
2197  if ( isset( $plink['active'] ) ) {
2198  $ptool['active'] = $plink['active'];
2199  }
2200  foreach ( [
2201  'href',
2202  'class',
2203  'text',
2204  'dir',
2205  'data',
2206  'exists',
2207  'data-mw'
2208  ] as $k ) {
2209  if ( isset( $plink[$k] ) ) {
2210  $ptool['links'][0][$k] = $plink[$k];
2211  }
2212  }
2213  $personal_tools[$key] = $ptool;
2214  }
2215  return $personal_tools;
2216  }
2217 
2271  final public function makeLink( $key, $item, $options = [] ) {
2272  $text = $item['text'] ?? $this->msg( $item['msg'] ?? $key )->text();
2273 
2274  $html = htmlspecialchars( $text );
2275 
2276  if ( isset( $options['text-wrapper'] ) ) {
2277  $wrapper = $options['text-wrapper'];
2278  if ( isset( $wrapper['tag'] ) ) {
2279  $wrapper = [ $wrapper ];
2280  }
2281  while ( count( $wrapper ) > 0 ) {
2282  $element = array_pop( $wrapper );
2283  $html = Html::rawElement( $element['tag'], $element['attributes'] ?? null, $html );
2284  }
2285  }
2286 
2287  if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
2288  $attrs = $item;
2289  foreach ( [ 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary',
2290  'tooltip-params', 'exists' ] as $k ) {
2291  unset( $attrs[$k] );
2292  }
2293 
2294  if ( isset( $attrs['data'] ) ) {
2295  foreach ( $attrs['data'] as $key => $value ) {
2296  $attrs[ 'data-' . $key ] = $value;
2297  }
2298  unset( $attrs[ 'data' ] );
2299  }
2300 
2301  if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
2302  $item['single-id'] = $item['id'];
2303  }
2304 
2305  $tooltipParams = [];
2306  if ( isset( $item['tooltip-params'] ) ) {
2307  $tooltipParams = $item['tooltip-params'];
2308  }
2309 
2310  if ( isset( $item['single-id'] ) ) {
2311  $tooltipOption = isset( $item['exists'] ) && $item['exists'] === false ? 'nonexisting' : null;
2312 
2313  if ( isset( $item['tooltiponly'] ) && $item['tooltiponly'] ) {
2314  $title = Linker::titleAttrib( $item['single-id'], $tooltipOption, $tooltipParams );
2315  if ( $title !== false ) {
2316  $attrs['title'] = $title;
2317  }
2318  } else {
2320  $item['single-id'],
2321  $tooltipParams,
2322  $tooltipOption
2323  );
2324  if ( isset( $tip['title'] ) && $tip['title'] !== false ) {
2325  $attrs['title'] = $tip['title'];
2326  }
2327  if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) {
2328  $attrs['accesskey'] = $tip['accesskey'];
2329  }
2330  }
2331  }
2332  if ( isset( $options['link-class'] ) ) {
2333  if ( isset( $attrs['class'] ) ) {
2334  $attrs['class'] .= " {$options['link-class']}";
2335  } else {
2336  $attrs['class'] = $options['link-class'];
2337  }
2338  }
2339  $html = Html::rawElement( isset( $attrs['href'] )
2340  ? 'a'
2341  : $options['link-fallback'], $attrs, $html );
2342  }
2343 
2344  return $html;
2345  }
2346 
2379  final public function makeListItem( $key, $item, $options = [] ) {
2380  // In case this is still set from SkinTemplate, we don't want it to appear in
2381  // the HTML output (normally removed in SkinTemplate::buildContentActionUrls())
2382  unset( $item['redundant'] );
2383 
2384  if ( isset( $item['links'] ) ) {
2385  $links = [];
2386  foreach ( $item['links'] as $linkKey => $link ) {
2387  $links[] = $this->makeLink( $linkKey, $link, $options );
2388  }
2389  $html = implode( ' ', $links );
2390  } else {
2391  $link = $item;
2392  // These keys are used by makeListItem and shouldn't be passed on to the link
2393  foreach ( [ 'id', 'class', 'active', 'tag', 'itemtitle' ] as $k ) {
2394  unset( $link[$k] );
2395  }
2396  if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
2397  // The id goes on the <li> not on the <a> for single links
2398  // but makeSidebarLink still needs to know what id to use when
2399  // generating tooltips and accesskeys.
2400  $link['single-id'] = $item['id'];
2401  }
2402  if ( isset( $link['link-class'] ) ) {
2403  // link-class should be set on the <a> itself,
2404  // so pass it in as 'class'
2405  $link['class'] = $link['link-class'];
2406  unset( $link['link-class'] );
2407  }
2408  $html = $this->makeLink( $key, $link, $options );
2409  }
2410 
2411  $attrs = [];
2412  foreach ( [ 'id', 'class' ] as $attr ) {
2413  if ( isset( $item[$attr] ) ) {
2414  $attrs[$attr] = $item[$attr];
2415  }
2416  }
2417  if ( isset( $item['active'] ) && $item['active'] ) {
2418  if ( !isset( $attrs['class'] ) ) {
2419  $attrs['class'] = '';
2420  }
2421  $attrs['class'] .= ' active';
2422  $attrs['class'] = trim( $attrs['class'] );
2423  }
2424  if ( isset( $item['itemtitle'] ) ) {
2425  $attrs['title'] = $item['itemtitle'];
2426  }
2427  return Html::rawElement( $options['tag'] ?? 'li', $attrs, $html );
2428  }
2429 
2436  final public function makeSearchInput( $attrs = [] ) {
2437  $realAttrs = [
2438  'type' => 'search',
2439  'name' => 'search',
2440  'placeholder' => $this->msg( 'searchsuggest-search' )->text(),
2441  ];
2442  $realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
2443  return Html::element( 'input', $realAttrs );
2444  }
2445 
2453  final public function makeSearchButton( $mode, $attrs = [] ) {
2454  switch ( $mode ) {
2455  case 'go':
2456  case 'fulltext':
2457  $realAttrs = [
2458  'type' => 'submit',
2459  'name' => $mode,
2460  'value' => $this->msg( $mode == 'go' ? 'searcharticle' : 'searchbutton' )->text(),
2461  ];
2462  $realAttrs = array_merge(
2463  $realAttrs,
2464  Linker::tooltipAndAccesskeyAttribs( "search-$mode" ),
2465  $attrs
2466  );
2467  return Html::element( 'input', $realAttrs );
2468  case 'image':
2469  $buttonAttrs = [
2470  'type' => 'submit',
2471  'name' => 'button',
2472  ];
2473  $buttonAttrs = array_merge(
2474  $buttonAttrs,
2475  Linker::tooltipAndAccesskeyAttribs( 'search-fulltext' ),
2476  $attrs
2477  );
2478  unset( $buttonAttrs['src'] );
2479  unset( $buttonAttrs['alt'] );
2480  unset( $buttonAttrs['width'] );
2481  unset( $buttonAttrs['height'] );
2482  $imgAttrs = [
2483  'src' => $attrs['src'],
2484  'alt' => $attrs['alt'] ?? $this->msg( 'searchbutton' )->text(),
2485  'width' => $attrs['width'] ?? null,
2486  'height' => $attrs['height'] ?? null,
2487  ];
2488  return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
2489  default:
2490  throw new MWException( 'Unknown mode passed to BaseTemplate::makeSearchButton' );
2491  }
2492  }
2493 
2504  public function getAfterPortlet( string $name ) : string {
2505  $html = '';
2506 
2507  $this->getHookRunner()->onSkinAfterPortlet( $this, $name, $html );
2508 
2509  return $html;
2510  }
2511 
2517  final public function prepareSubtitle() {
2518  $out = $this->getOutput();
2519  $subpagestr = $this->subPageSubtitle();
2520  if ( $subpagestr !== '' ) {
2521  $subpagestr = '<span class="subpages">' . $subpagestr . '</span>';
2522  }
2523  return $subpagestr . $out->getSubtitle();
2524  }
2525 
2537  protected function getFooterLinks(): array {
2538  $out = $this->getOutput();
2539  $title = $out->getTitle();
2540  $titleExists = $title->exists();
2541  $config = $this->getConfig();
2542  $maxCredits = $config->get( 'MaxCredits' );
2543  $showCreditsIfMax = $config->get( 'ShowCreditsIfMax' );
2544  $useCredits = $titleExists
2545  && $out->isArticle()
2546  && $out->isRevisionCurrent()
2547  && $maxCredits !== 0;
2548 
2550  if ( $titleExists ) {
2551  $article = Article::newFromWikiPage( $this->getWikiPage(), $this );
2552  $action = Action::factory( 'credits', $article, $this );
2553  }
2554 
2555  '@phan-var CreditsAction $action';
2556  $data = [
2557  'info' => [
2558  'lastmod' => !$useCredits ? $this->lastModified() : null,
2559  'numberofwatchingusers' => null,
2560  'credits' => $useCredits ?
2561  $action->getCredits( $maxCredits, $showCreditsIfMax ) : null,
2562  'copyright' => $titleExists &&
2563  $out->showsCopyright() ? $this->getCopyright() : null,
2564  ],
2565  'places' => $this->getSiteFooterLinks(),
2566  ];
2567  foreach ( $data as $key => $existingItems ) {
2568  $newItems = [];
2569  $this->getHookRunner()->onSkinAddFooterLinks( $this, $key, $newItems );
2570  $data[$key] = $existingItems + $newItems;
2571  }
2572  return $data;
2573  }
2574 
2575 }
Skin\prepareSubtitle
prepareSubtitle()
Prepare the subtitle of the page for output in the skin if one has been set.
Definition: Skin.php:2517
OutputPage\addMeta
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
Definition: OutputPage.php:405
Skin\$skinname
string null $skinname
Definition: Skin.php:48
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:70
Skin\editUrlOptions
editUrlOptions()
Return URL options for the 'edit page' link.
Definition: Skin.php:1185
Skin\showEmailUser
showEmailUser( $id)
Definition: Skin.php:1200
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:584
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:328
Skin\makeUrlDetails
static makeUrlDetails( $name, $urlaction='')
these return an array with the 'href' and boolean 'exists'
Definition: Skin.php:1329
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:45
Skin\getSiteNotice
getSiteNotice()
Get the site notice.
Definition: Skin.php:1981
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
UploadBase\isAllowed
static isAllowed(UserIdentity $user)
Returns true if the user can use this upload module or else a string identifying the missing permissi...
Definition: UploadBase.php:155
ResourceLoader\makeConfigSetScript
static makeConfigSetScript(array $configuration)
Return JS code which will set the MediaWiki configuration array to the given value.
Definition: ResourceLoader.php:1670
Skin\buildFeedUrls
buildFeedUrls()
Build data structure representing syndication links.
Definition: Skin.php:1626
Skin\$options
array $options
Skin options passed into constructor.
Definition: Skin.php:53
Skin\footerLinkTitle
footerLinkTitle( $desc, $page)
Definition: Skin.php:1094
LinkBatch
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:35
Skin\getPoweredBy
getPoweredBy()
Gets the powered by MediaWiki icon.
Definition: Skin.php:937
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:163
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:81
ResourceLoader\makeInlineScript
static makeInlineScript( $script, $nonce=null)
Make an HTML script that runs given JS code after startup and base modules.
Definition: ResourceLoader.php:1643
Sanitizer\escapeIdForAttribute
static escapeIdForAttribute( $id, $mode=self::ID_PRIMARY)
Given a section name or other user-generated or otherwise unsafe string, escapes it to be a valid HTM...
Definition: Sanitizer.php:815
true
return true
Definition: router.php:90
Skin\makeVariablesScript
static makeVariablesScript( $data, $nonce=null)
Definition: Skin.php:438
$fallback
$fallback
Definition: MessagesAb.php:11
Skin\lastModified
lastModified()
Get the timestamp of the latest revision, formatted in user language.
Definition: Skin.php:960
Skin\getSiteFooterLinks
getSiteFooterLinks()
Gets the link to the wiki's privacy policy, about page, and disclaimer page.
Definition: Skin.php:1114
UploadBase\isEnabled
static isEnabled()
Returns true if uploads are enabled.
Definition: UploadBase.php:141
Skin\checkTitle
static checkTitle(&$title, $name)
make sure we have some title to operate on
Definition: Skin.php:1361
MWDebug\getHTMLDebugLog
static getHTMLDebugLog()
Generate debug log in HTML for displaying at the bottom of the main content area.
Definition: MWDebug.php:597
Skin\mapInterwikiToLanguage
mapInterwikiToLanguage( $code)
Allows correcting the language of interlanguage links which, mostly due to legacy reasons,...
Definition: Skin.php:1378
Skin\makeSpecialUrl
static makeSpecialUrl( $name, $urlaction='', $proto=null)
Make a URL for a Special Page using the given query and protocol.
Definition: Skin.php:1257
Skin\getCategoryLinks
getCategoryLinks()
Definition: Skin.php:526
Skin\aboutLink
aboutLink()
Gets the link to the wiki's about page.
Definition: Skin.php:1162
Skin\makeSpecialUrlSubpage
static makeSpecialUrlSubpage( $name, $subpage, $urlaction='')
Definition: Skin.php:1272
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:545
$s
$s
Definition: mergeMessageFileList.php:184
SpecialPage\getTitleFor
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,...
Definition: SpecialPage.php:92
Sanitizer\escapeClass
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:976
ContextSource\getRequest
getRequest()
Definition: ContextSource.php:79
Title\newMainPage
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:653
Skin\$mRelevantUser
$mRelevantUser
Definition: Skin.php:55
Action\factory
static factory(?string $action, Page $article, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:115
ContextSource\getUser
getUser()
Stable to override.
Definition: ContextSource.php:134
ContextSource\getTitle
getTitle()
Definition: ContextSource.php:88
wfExpandIRI
wfExpandIRI( $url)
Take a URL, make sure it's expanded to fully qualified, and replace any encoded non-ASCII Unicode cha...
Definition: GlobalFunctions.php:860
WikiMap\getCurrentWikiId
static getCurrentWikiId()
Definition: WikiMap.php:303
Skin\getHtmlElementAttributes
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:503
SpecialEmailUser\validateTarget
static validateTarget( $target, User $sender)
Validate target User.
Definition: SpecialEmailUser.php:218
Skin\doEditSectionLink
doEditSectionLink(Title $nt, $section, $tooltip, Language $lang)
Create a section edit link.
Definition: Skin.php:2017
Skin\makeListItem
makeListItem( $key, $item, $options=[])
Generates a list item for a navigation, portlet, portal, sidebar...
Definition: Skin.php:2379
Revision\RevisionLookup
Service for looking up page revisions.
Definition: RevisionLookup.php:38
Skin\bottomScripts
bottomScripts()
This gets called shortly before the "</body>" tag.
Definition: Skin.php:693
Skin\mainPageLink
mainPageLink()
Gets the link to the wiki's main page.
Definition: Skin.php:1046
Linker\tooltipAndAccesskeyAttribs
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2294
Skin\getSkinNames
static getSkinNames()
Fetch the set of available skins.
Definition: Skin.php:69
Skin\afterContentHook
afterContentHook()
This runs a hook to allow extensions placing their stuff after content and article metadata (e....
Definition: Skin.php:655
ContextSource\getLanguage
getLanguage()
Definition: ContextSource.php:143
SpecialPage\getSafeTitleFor
static getSafeTitleFor( $name, $subpage=false)
Get a localised Title object for a page name with a possibly unvalidated subpage.
Definition: SpecialPage.php:121
NS_MAIN
const NS_MAIN
Definition: Defines.php:69
Skin\getCachedNotice
getCachedNotice( $name)
Get a cached notice.
Definition: Skin.php:1932
UserrightsPage
Special page to allow managing user group membership.
Definition: SpecialUserrights.php:32
Article\newFromWikiPage
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:228
MWException
MediaWiki exception.
Definition: MWException.php:29
Skin\getIndicatorsData
getIndicatorsData( $indicators)
Return an array of indicator data.
Definition: Skin.php:2161
Skin\buildSidebar
buildSidebar()
Build an array that represents the sidebar(s), the navigation bar among them.
Definition: Skin.php:1665
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1027
Skin\privacyLink
privacyLink()
Gets the link to the wiki's privacy policy page.
Definition: Skin.php:1151
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:101
Skin\footerLink
footerLink( $desc, $page)
Given a pair of message keys for link and text label, return an HTML link for use in the footer.
Definition: Skin.php:1075
Skin\generateDebugHTML
generateDebugHTML()
Generate debug data HTML for displaying at the bottom of the main content area.
Definition: Skin.php:683
getPermissionManager
getPermissionManager()
$wgFallbackSkin
$wgFallbackSkin
Fallback skin used when the skin defined by $wgDefaultSkin can't be found.
Definition: DefaultSettings.php:3461
Skin\$stylename
string $stylename
Stylesheets set to use.
Definition: Skin.php:61
Skin\getCategories
getCategories()
Definition: Skin.php:622
Skin\setRelevantUser
setRelevantUser( $u)
Set the "relevant" user.
Definition: Skin.php:390
ContextSource\getOutput
getOutput()
Definition: ContextSource.php:124
ContextSource
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
Definition: ContextSource.php:31
ContextSource\getWikiPage
getWikiPage()
Get the WikiPage object.
Definition: ContextSource.php:115
$modules
$modules
Definition: HTMLFormElement.php:15
Skin\addToSidebar
addToSidebar(&$bar, $message)
Add content from a sidebar system message Currently only used for MediaWiki:Sidebar (but may be used ...
Definition: Skin.php:1720
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:931
Skin\isRevisionCurrent
isRevisionCurrent()
Whether the revision displayed is the latest revision of the page.
Definition: Skin.php:356
Skin\drawCategoryBrowser
drawCategoryBrowser( $tree)
Render the array as a series of links.
Definition: Skin.php:598
Skin\getLanguages
getLanguages()
Generates array of language links for the current page.
Definition: Skin.php:1391
Skin\makeNSUrl
static makeNSUrl( $name, $urlaction='', $namespace=NS_MAIN)
this can be passed the NS number as defined in Language.php
Definition: Skin.php:1315
$title
$title
Definition: testCompression.php:38
Skin\preloadExistence
preloadExistence()
Preload the existence of three commonly-requested pages in a single query.
Definition: Skin.php:296
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:846
$wgDefaultSkin
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
Definition: DefaultSettings.php:3454
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:83
Skin\disclaimerLink
disclaimerLink()
Gets the link to the wiki's general disclaimers page.
Definition: Skin.php:1173
Skin\getLogo
getLogo()
URL to the default square logo (1x key)
Definition: Skin.php:518
$revStore
$revStore
Definition: testCompression.php:55
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:910
OutputPage
This is one of the Core classes and should be read at least once by any new developers.
Definition: OutputPage.php:47
Skin\makeToolbox
makeToolbox( $navUrls, $feedUrls)
Create an array of common toolbox items from the data in the quicktemplate stored by SkinTemplate.
Definition: Skin.php:2071
ResourceLoaderSkinModule\getAvailableLogos
static getAvailableLogos( $conf)
Return an array of all available logos that a skin may use.
Definition: ResourceLoaderSkinModule.php:379
Skin\$mRelevantTitle
$mRelevantTitle
Definition: Skin.php:54
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:187
wfUrlProtocols
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
Definition: GlobalFunctions.php:718
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
Skin\getRelevantTitle
getRelevantTitle()
Return the "relevant" title.
Definition: Skin.php:381
$content
$content
Definition: router.php:76
Skin\getSkinStylePath
getSkinStylePath( $name)
Return a fully resolved style path URL to images or styles stored in the current skin's folder.
Definition: Skin.php:1224
Skin\getPageClasses
getPageClasses( $title)
TODO: document.
Definition: Skin.php:465
Skin\makeSearchInput
makeSearchInput( $attrs=[])
Definition: Skin.php:2436
Skin\getDefaultModules
getDefaultModules()
Defines the ResourceLoader modules that should be added to the skin It is recommended that skins wish...
Definition: Skin.php:214
Skin\getIndicatorsHTML
getIndicatorsHTML( $indicators)
Get the suggested HTML for page status indicators: icons (or short text snippets) usually displayed i...
Definition: Skin.php:2137
Skin\getSearchLink
getSearchLink()
Definition: Skin.php:853
$line
$line
Definition: mcc.php:119
Skin\getPersonalToolsForMakeListItem
getPersonalToolsForMakeListItem( $urls)
Create an array of personal tools items from the data in the quicktemplate stored by SkinTemplate.
Definition: Skin.php:2185
Skin\printSource
printSource()
Text with the permalink to the source page, usually shown on the footer of a printed page.
Definition: Skin.php:716
Skin\buildNavUrls
buildNavUrls()
Build array of common navigation links.
Definition: Skin.php:1497
Skin\getAfterPortlet
getAfterPortlet(string $name)
Allows extensions to hook into known portlets and add stuff to them.
Definition: Skin.php:2504
Skin\__construct
__construct( $options=null)
Definition: Skin.php:153
Linker\titleAttrib
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:2110
$lines
if(!file_exists( $CREDITS)) $lines
Definition: updateCredits.php:49
Skin\logoText
logoText( $align='')
Definition: Skin.php:994
Skin\getNewtalks
getNewtalks()
Gets new talk page messages for the current user and returns an appropriate alert message (or an empt...
Definition: Skin.php:1825
Title
Represents a title within MediaWiki.
Definition: Title.php:41
Skin\makeUrl
static makeUrl( $name, $urlaction='')
Definition: Skin.php:1283
wfMatchesDomainList
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
Definition: GlobalFunctions.php:876
$cache
$cache
Definition: mcc.php:33
LanguageCode\bcp47
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
Definition: LanguageCode.php:175
Skin\getCopyright
getCopyright( $type='detect')
Definition: Skin.php:864
Html\rawElement
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
SpecialPage\setContext
setContext( $context)
Sets the context this SpecialPage is executed in.
Definition: SpecialPage.php:707
Skin\getRelevantUser
getRelevantUser()
Return the "relevant" user.
Definition: Skin.php:402
Skin\subPageSubtitle
subPageSubtitle( $out=null)
Definition: Skin.php:795
NS_USER
const NS_USER
Definition: Defines.php:71
SkinException
Exceptions for skin-related failures.
Definition: SkinException.php:29
Skin\getUndeleteLink
getUndeleteLink()
Definition: Skin.php:735
Skin\makeKnownUrlDetails
static makeKnownUrlDetails( $name, $urlaction='')
Make URL details where the article exists (or at least it's convenient to think so)
Definition: Skin.php:1345
Skin\setupSkinUserCss
setupSkinUserCss(OutputPage $out)
Hook point for adding style modules to OutputPage.
Definition: Skin.php:456
CreditsAction
Definition: CreditsAction.php:31
Skin\makeMainPageUrl
static makeMainPageUrl( $urlaction='')
Definition: Skin.php:1239
Skin\getFooterLinks
getFooterLinks()
Get template representation of the footer containing site footer links as well as standard footer lin...
Definition: Skin.php:2537
$t
$t
Definition: testCompression.php:74
Skin\getCopyrightIcon
getCopyrightIcon()
Definition: Skin.php:907
Skin\outputPage
outputPage()
Outputs the HTML generated by other functions.
Html\element
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
Skin\getSkinName
getSkinName()
Definition: Skin.php:169
Skin\getAllowedSkins
static getAllowedSkins()
Fetch the list of user-selectable skins in regards to $wgSkipSkins.
Definition: Skin.php:85
Skin
The main skin class which provides methods and properties for all other skins.
Definition: Skin.php:42
Skin\initPage
initPage(OutputPage $out)
Stable to override.
Definition: Skin.php:190
Skin\setRelevantTitle
setRelevantTitle( $t)
Set the "relevant" title.
Definition: Skin.php:367
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
Skin\isResponsive
isResponsive()
Indicates if this skin is responsive.
Definition: Skin.php:182
Skin\makeSearchButton
makeSearchButton( $mode, $attrs=[])
Definition: Skin.php:2453
Skin\getRevisionId
getRevisionId()
Get the current revision ID.
Definition: Skin.php:344
Skin\addToSidebarPlain
addToSidebarPlain(&$bar, $text)
Add content from plain text.
Definition: Skin.php:1731
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:41
Skin\makeLink
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:2271
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1297
Skin\makeFooterIcon
makeFooterIcon( $icon, $withImage='withImage')
Renders a $wgFooterIcons icon according to the method's arguments.
Definition: Skin.php:1017
$type
$type
Definition: testCompression.php:52