MediaWiki  master
Skin.php
Go to the documentation of this file.
1 <?php
23 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
26 use Wikimedia\WrappedString;
27 use Wikimedia\WrappedStringList;
28 
41 abstract 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 
153  public function __construct( $options = null ) {
154  if ( is_string( $options ) ) {
155  $this->skinname = $options;
156  } elseif ( $options ) {
157  $this->options = $options;
158  $name = $options['name'] ?? null;
159  // Note: skins might override the public $skinname method instead
160  if ( $name ) {
161  $this->skinname = $name;
162  }
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.startup',
234  ],
235  // modules that enhance the content in some way
236  'content' => [
237  'mediawiki.page.ready',
238  ],
239  // modules relating to search functionality
240  'search' => [],
241  // Skins can register their own scripts
242  'skin' => $this->options['scripts'] ?? [],
243  // modules relating to functionality relating to watching an article
244  'watch' => [],
245  // modules which relate to the current users preferences
246  'user' => [],
247  // modules relating to RSS/Atom Feeds
248  'syndicate' => [],
249  ];
250 
251  // Preload jquery.tablesorter for mediawiki.page.ready
252  if ( strpos( $out->getHTML(), 'sortable' ) !== false ) {
253  $modules['content'][] = 'jquery.tablesorter';
254  $modules['styles']['content'][] = 'jquery.tablesorter.styles';
255  }
256 
257  // Preload jquery.makeCollapsible for mediawiki.page.ready
258  if ( strpos( $out->getHTML(), 'mw-collapsible' ) !== false ) {
259  $modules['content'][] = 'jquery.makeCollapsible';
260  $modules['styles']['content'][] = 'jquery.makeCollapsible.styles';
261  }
262 
263  // Deprecated since 1.26: Unconditional loading of mediawiki.ui.button
264  // on every page is deprecated. Express a dependency instead.
265  if ( strpos( $out->getHTML(), 'mw-ui-button' ) !== false ) {
266  $modules['styles']['content'][] = 'mediawiki.ui.button';
267  }
268 
269  if ( $out->isTOCEnabled() ) {
270  $modules['content'][] = 'mediawiki.toc';
271  $modules['styles']['content'][] = 'mediawiki.toc.styles';
272  }
273 
274  $prefMgr = MediaWikiServices::getInstance()->getPermissionManager();
275  if ( $user->isLoggedIn()
276  && $prefMgr->userHasAllRights( $user, 'writeapi', 'viewmywatchlist', 'editmywatchlist' )
277  && $this->getRelevantTitle()->canExist()
278  ) {
279  $modules['watch'][] = 'mediawiki.page.watch.ajax';
280  }
281 
282  if ( $user->getBoolOption( 'editsectiononrightclick' )
283  || ( $out->isArticle() && $user->getOption( 'editondblclick' ) )
284  ) {
285  $modules['user'][] = 'mediawiki.misc-authed-pref';
286  }
287 
288  if ( $out->isSyndicated() ) {
289  $modules['styles']['syndicate'][] = 'mediawiki.feedlink';
290  }
291 
292  return $modules;
293  }
294 
298  protected function preloadExistence() {
299  $titles = [];
300 
301  // User/talk link
302  $user = $this->getUser();
303  if ( $user->isLoggedIn() ) {
304  $titles[] = $user->getUserPage();
305  $titles[] = $user->getTalkPage();
306  }
307 
308  // Check, if the page can hold some kind of content, otherwise do nothing
309  $title = $this->getRelevantTitle();
310  if ( $title->canExist() && $title->canHaveTalkPage() ) {
311  $namespaceInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
312  if ( $title->isTalkPage() ) {
313  $titles[] = $namespaceInfo->getSubjectPage( $title );
314  } else {
315  $titles[] = $namespaceInfo->getTalkPage( $title );
316  }
317  }
318 
319  // Footer links (used by SkinTemplate::prepareQuickTemplate)
320  if ( $this->getConfig()->get( 'FooterLinkCacheExpiry' ) <= 0 ) {
321  $titles = array_merge(
322  $titles,
323  array_filter( [
324  $this->footerLinkTitle( 'privacy', 'privacypage' ),
325  $this->footerLinkTitle( 'aboutsite', 'aboutpage' ),
326  $this->footerLinkTitle( 'disclaimers', 'disclaimerpage' ),
327  ] )
328  );
329  }
330 
331  $this->getHookRunner()->onSkinPreloadExistence( $titles, $this );
332 
333  if ( $titles ) {
334  $lb = new LinkBatch( $titles );
335  $lb->setCaller( __METHOD__ );
336  $lb->execute();
337  }
338  }
339 
346  public function getRevisionId() {
347  wfDeprecated( __METHOD__, '1.34' );
348 
349  return $this->getOutput()->getRevisionId();
350  }
351 
358  public function isRevisionCurrent() {
359  wfDeprecated( __METHOD__, '1.34' );
360 
361  return $this->getOutput()->isRevisionCurrent();
362  }
363 
369  public function setRelevantTitle( $t ) {
370  $this->mRelevantTitle = $t;
371  }
372 
383  public function getRelevantTitle() {
384  return $this->mRelevantTitle ?? $this->getTitle();
385  }
386 
392  public function setRelevantUser( $u ) {
393  $this->mRelevantUser = $u;
394  }
395 
404  public function getRelevantUser() {
405  if ( isset( $this->mRelevantUser ) ) {
406  return $this->mRelevantUser;
407  }
408  $title = $this->getRelevantTitle();
409  if ( $title->hasSubjectNamespace( NS_USER ) ) {
410  $rootUser = $title->getRootText();
411  if ( User::isIP( $rootUser ) ) {
412  $this->mRelevantUser = User::newFromName( $rootUser, false );
413  } else {
414  $user = User::newFromName( $rootUser, false );
415 
416  if ( $user ) {
417  $user->load( User::READ_NORMAL );
418 
419  if ( $user->isLoggedIn() ) {
420  $this->mRelevantUser = $user;
421  }
422  }
423  }
424  return $this->mRelevantUser;
425  }
426  return null;
427  }
428 
432  abstract public function outputPage();
433 
439  public static function makeVariablesScript( $data, $nonce = null ) {
440  if ( $data ) {
443  $nonce
444  );
445  }
446  return '';
447  }
448 
455  public function setupSkinUserCss( OutputPage $out ) {
456  // Stub.
457  }
458 
464  public function getPageClasses( $title ) {
465  $numeric = 'ns-' . $title->getNamespace();
466  $user = $this->getUser();
467 
468  if ( $title->isSpecialPage() ) {
469  $type = 'ns-special';
470  // T25315: provide a class based on the canonical special page name without subpages
471  list( $canonicalName ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
472  resolveAlias( $title->getDBkey() );
473  if ( $canonicalName ) {
474  $type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" );
475  } else {
476  $type .= ' mw-invalidspecialpage';
477  }
478  } else {
479  if ( $title->isTalkPage() ) {
480  $type = 'ns-talk';
481  } else {
482  $type = 'ns-subject';
483  }
484  // T208315: add HTML class when the user can edit the page
485  if ( MediaWikiServices::getInstance()->getPermissionManager()
486  ->quickUserCan( 'edit', $user, $title )
487  ) {
488  $type .= ' mw-editable';
489  }
490  }
491 
492  $name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
493  $root = Sanitizer::escapeClass( 'rootpage-' . $title->getRootTitle()->getPrefixedText() );
494 
495  return "$numeric $type $name $root";
496  }
497 
502  public function getHtmlElementAttributes() {
503  $lang = $this->getLanguage();
504  return [
505  'lang' => $lang->getHtmlCode(),
506  'dir' => $lang->getDir(),
507  'class' => 'client-nojs',
508  ];
509  }
510 
516  protected function getLogo() {
517  return ResourceLoaderSkinModule::getAvailableLogos( $this->getConfig() )[ '1x' ];
518  }
519 
523  public function getCategoryLinks() {
524  $out = $this->getOutput();
525  $allCats = $out->getCategoryLinks();
526  $title = $this->getTitle();
527  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
528 
529  if ( $allCats === [] ) {
530  return '';
531  }
532 
533  $embed = "<li>";
534  $pop = "</li>";
535 
536  $s = '';
537  $colon = $this->msg( 'colon-separator' )->escaped();
538 
539  if ( !empty( $allCats['normal'] ) ) {
540  $t = $embed . implode( $pop . $embed, $allCats['normal'] ) . $pop;
541 
542  $msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) );
543  $linkPage = $this->msg( 'pagecategorieslink' )->inContentLanguage()->text();
544  $pageCategoriesLinkTitle = Title::newFromText( $linkPage );
545  if ( $pageCategoriesLinkTitle ) {
546  $link = $linkRenderer->makeLink( $pageCategoriesLinkTitle, $msg->text() );
547  } else {
548  $link = $msg->escaped();
549  }
550  $s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
551  $link . $colon . '<ul>' . $t . '</ul></div>';
552  }
553 
554  # Hidden categories
555  if ( isset( $allCats['hidden'] ) ) {
556  if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) {
557  $class = ' mw-hidden-cats-user-shown';
558  } elseif ( $title->inNamespace( NS_CATEGORY ) ) {
559  $class = ' mw-hidden-cats-ns-shown';
560  } else {
561  $class = ' mw-hidden-cats-hidden';
562  }
563 
564  $s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
565  $this->msg( 'hidden-categories' )->numParams( count( $allCats['hidden'] ) )->escaped() .
566  $colon . '<ul>' . $embed . implode( $pop . $embed, $allCats['hidden'] ) . $pop . '</ul>' .
567  '</div>';
568  }
569 
570  # optional 'dmoz-like' category browser. Will be shown under the list
571  # of categories an article belong to
572  if ( $this->getConfig()->get( 'UseCategoryBrowser' ) ) {
573  $s .= '<br /><hr />';
574 
575  # get a big array of the parents tree
576  $parenttree = $title->getParentCategoryTree();
577  # Skin object passed by reference cause it can not be
578  # accessed under the method subfunction drawCategoryBrowser
579  $tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree ) );
580  # Clean out bogus first entry and sort them
581  unset( $tempout[0] );
582  asort( $tempout );
583  # Output one per line
584  $s .= implode( "<br />\n", $tempout );
585  }
586 
587  return $s;
588  }
589 
595  protected function drawCategoryBrowser( $tree ) {
596  $return = '';
597  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
598 
599  foreach ( $tree as $element => $parent ) {
600  if ( empty( $parent ) ) {
601  # element start a new list
602  $return .= "\n";
603  } else {
604  # grab the others elements
605  $return .= $this->drawCategoryBrowser( $parent ) . ' &gt; ';
606  }
607 
608  # add our current element to the list
609  $eltitle = Title::newFromText( $element );
610  $return .= $linkRenderer->makeLink( $eltitle, $eltitle->getText() );
611  }
612 
613  return $return;
614  }
615 
619  public function getCategories() {
620  $catlinks = $this->getCategoryLinks();
621  // Check what we're showing
622  $allCats = $this->getOutput()->getCategoryLinks();
623  $showHidden = $this->getUser()->getBoolOption( 'showhiddencats' ) ||
624  $this->getTitle()->inNamespace( NS_CATEGORY );
625 
626  $classes = [ 'catlinks' ];
627  if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
628  $classes[] = 'catlinks-allhidden';
629  }
630 
631  return Html::rawElement(
632  'div',
633  [ 'id' => 'catlinks', 'class' => $classes, 'data-mw' => 'interface' ],
634  $catlinks
635  );
636  }
637 
652  protected function afterContentHook() {
653  $data = '';
654 
655  if ( $this->getHookRunner()->onSkinAfterContent( $data, $this ) ) {
656  // adding just some spaces shouldn't toggle the output
657  // of the whole <div/>, so we use trim() here
658  if ( trim( $data ) != '' ) {
659  // Doing this here instead of in the skins to
660  // ensure that the div has the same ID in all
661  // skins
662  $data = "<div id='mw-data-after-content'>\n" .
663  "\t$data\n" .
664  "</div>\n";
665  }
666  } else {
667  wfDebug( "Hook SkinAfterContent changed output processing." );
668  }
669 
670  return $data;
671  }
672 
680  protected function generateDebugHTML() {
681  wfDeprecated( __METHOD__, '1.35' );
682  return MWDebug::getHTMLDebugLog();
683  }
684 
690  public function bottomScripts() {
691  // TODO and the suckage continues. This function is really just a wrapper around
692  // OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned
693  // up at some point
694  $chunks = [ $this->getOutput()->getBottomScripts() ];
695 
696  // Keep the hook appendage separate to preserve WrappedString objects.
697  // This enables BaseTemplate::getTrail() to merge them where possible.
698  $extraHtml = '';
699  $this->getHookRunner()->onSkinAfterBottomScripts( $this, $extraHtml );
700  if ( $extraHtml !== '' ) {
701  $chunks[] = $extraHtml;
702  }
703  return WrappedString::join( "\n", $chunks );
704  }
705 
713  public function printSource() {
714  $title = $this->getTitle();
715  $oldid = $this->getOutput()->getRevisionId();
716  if ( $oldid ) {
717  $canonicalUrl = $title->getCanonicalURL( 'oldid=' . $oldid );
718  $url = htmlspecialchars( wfExpandIRI( $canonicalUrl ) );
719  } else {
720  // oldid not available for non existing pages
721  $url = htmlspecialchars( wfExpandIRI( $title->getCanonicalURL() ) );
722  }
723 
724  return $this->msg( 'retrievedfrom' )
725  ->rawParams( '<a dir="ltr" href="' . $url . '">' . $url . '</a>' )
726  ->parse();
727  }
728 
732  public function getUndeleteLink() {
733  $action = $this->getRequest()->getVal( 'action', 'view' );
734  $title = $this->getTitle();
735  $user = $this->getUser();
736  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
737  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
738 
739  if ( ( !$title->exists() || $action == 'history' ) &&
740  $permissionManager->quickUserCan( 'deletedhistory', $user, $title )
741  ) {
742  $n = $title->isDeleted();
743 
744  if ( $n ) {
745  if ( $permissionManager->quickUserCan( 'undelete', $user, $title ) ) {
746  $msg = 'thisisdeleted';
747  } else {
748  $msg = 'viewdeleted';
749  }
750 
751  $subtitle = $this->msg( $msg )->rawParams(
752  $linkRenderer->makeKnownLink(
753  SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() ),
754  $this->msg( 'restorelink' )->numParams( $n )->text() )
755  )->escaped();
756 
757  // Allow extensions to add more links
758  $links = [];
759  $this->getHookRunner()->onUndeletePageToolLinks(
760  $this->getContext(), $linkRenderer, $links );
761 
762  if ( $links ) {
763  $subtitle .= ''
764  . $this->msg( 'word-separator' )->escaped()
765  . $this->msg( 'parentheses' )
766  ->rawParams( $this->getLanguage()->pipeList( $links ) )
767  ->escaped();
768  }
769  return Html::rawElement( 'div', [ 'class' => 'mw-undelete-subtitle' ], $subtitle );
770  }
771  }
772 
773  return '';
774  }
775 
780  public function subPageSubtitle( $out = null ) {
781  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
782  $out = $out ?? $this->getOutput();
783  $title = $out->getTitle();
784  $subpages = '';
785 
786  if ( !$this->getHookRunner()->onSkinSubPageSubtitle( $subpages, $this, $out ) ) {
787  return $subpages;
788  }
789 
790  if (
791  $out->isArticle() && MediaWikiServices::getInstance()->getNamespaceInfo()->
792  hasSubpages( $title->getNamespace() )
793  ) {
794  $ptext = $title->getPrefixedText();
795  if ( strpos( $ptext, '/' ) !== false ) {
796  $links = explode( '/', $ptext );
797  array_pop( $links );
798  $c = 0;
799  $growinglink = '';
800  $display = '';
801  $lang = $this->getLanguage();
802 
803  foreach ( $links as $link ) {
804  $growinglink .= $link;
805  $display .= $link;
806  $linkObj = Title::newFromText( $growinglink );
807 
808  if ( is_object( $linkObj ) && $linkObj->isKnown() ) {
809  $getlink = $linkRenderer->makeKnownLink(
810  $linkObj, $display
811  );
812 
813  $c++;
814 
815  if ( $c > 1 ) {
816  $subpages .= $lang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
817  } else {
818  $subpages .= '&lt; ';
819  }
820 
821  $subpages .= $getlink;
822  $display = '';
823  } else {
824  $display .= '/';
825  }
826  $growinglink .= '/';
827  }
828  }
829  }
830 
831  return $subpages;
832  }
833 
837  protected function getSearchLink() {
838  $searchPage = SpecialPage::getTitleFor( 'Search' );
839  return $searchPage->getLocalURL();
840  }
841 
846  public function getCopyright( $type = 'detect' ) {
847  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
848  if ( $type == 'detect' ) {
849  if ( !$this->getOutput()->isRevisionCurrent()
850  && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled()
851  ) {
852  $type = 'history';
853  } else {
854  $type = 'normal';
855  }
856  }
857 
858  if ( $type == 'history' ) {
859  $msg = 'history_copyright';
860  } else {
861  $msg = 'copyright';
862  }
863 
864  $config = $this->getConfig();
865 
866  if ( $config->get( 'RightsPage' ) ) {
867  $title = Title::newFromText( $config->get( 'RightsPage' ) );
868  $link = $linkRenderer->makeKnownLink(
869  $title, new HtmlArmor( $config->get( 'RightsText' ) )
870  );
871  } elseif ( $config->get( 'RightsUrl' ) ) {
872  $link = Linker::makeExternalLink( $config->get( 'RightsUrl' ), $config->get( 'RightsText' ) );
873  } elseif ( $config->get( 'RightsText' ) ) {
874  $link = $config->get( 'RightsText' );
875  } else {
876  # Give up now
877  return '';
878  }
879 
880  // Allow for site and per-namespace customization of copyright notice.
881  $this->getHookRunner()->onSkinCopyrightFooter( $this->getTitle(), $type, $msg, $link );
882 
883  return $this->msg( $msg )->rawParams( $link )->text();
884  }
885 
889  protected function getCopyrightIcon() {
890  $out = '';
891  $config = $this->getConfig();
892 
893  $footerIcons = $config->get( 'FooterIcons' );
894  if ( $footerIcons['copyright']['copyright'] ) {
895  $out = $footerIcons['copyright']['copyright'];
896  } elseif ( $config->get( 'RightsIcon' ) ) {
897  $icon = htmlspecialchars( $config->get( 'RightsIcon' ) );
898  $url = $config->get( 'RightsUrl' );
899 
900  if ( $url ) {
901  $out .= '<a href="' . htmlspecialchars( $url ) . '">';
902  }
903 
904  $text = htmlspecialchars( $config->get( 'RightsText' ) );
905  $out .= "<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
906 
907  if ( $url ) {
908  $out .= '</a>';
909  }
910  }
911 
912  return $out;
913  }
914 
919  protected function getPoweredBy() {
920  $resourceBasePath = $this->getConfig()->get( 'ResourceBasePath' );
921  $url1 = htmlspecialchars(
922  "$resourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
923  );
924  $url1_5 = htmlspecialchars(
925  "$resourceBasePath/resources/assets/poweredby_mediawiki_132x47.png"
926  );
927  $url2 = htmlspecialchars(
928  "$resourceBasePath/resources/assets/poweredby_mediawiki_176x62.png"
929  );
930  $text = '<a href="https://www.mediawiki.org/"><img src="' . $url1
931  . '" srcset="' . $url1_5 . ' 1.5x, ' . $url2 . ' 2x" '
932  . 'height="31" width="88" alt="Powered by MediaWiki" loading="lazy" /></a>';
933  $this->getHookRunner()->onSkinGetPoweredBy( $text, $this );
934  return $text;
935  }
936 
942  protected function lastModified() {
943  $timestamp = $this->getOutput()->getRevisionTimestamp();
944  $user = $this->getUser();
945  $language = $this->getLanguage();
946 
947  # No cached timestamp, load it from the database
948  if ( $timestamp === null ) {
949  $timestamp = MediaWikiServices::getInstance()
950  ->getRevisionLookup()
951  ->getTimestampFromId( $this->getOutput()->getRevisionId() );
952  }
953 
954  if ( $timestamp ) {
955  $d = $language->userDate( $timestamp, $user );
956  $t = $language->userTime( $timestamp, $user );
957  $s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->parse();
958  } else {
959  $s = '';
960  }
961 
962  if ( MediaWikiServices::getInstance()->getDBLoadBalancer()->getLaggedReplicaMode() ) {
963  $s .= ' <strong>' . $this->msg( 'laggedreplicamode' )->parse() . '</strong>';
964  }
965 
966  return $s;
967  }
968 
973  public function logoText( $align = '' ) {
974  if ( $align != '' ) {
975  $a = " style='float: {$align};'";
976  } else {
977  $a = '';
978  }
979 
980  $mp = $this->msg( 'mainpage' )->escaped();
981  $mptitle = Title::newMainPage();
982  $url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) : '' );
983 
984  $logourl = $this->getLogo();
985  return "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
986  }
987 
996  public function makeFooterIcon( $icon, $withImage = 'withImage' ) {
997  if ( is_string( $icon ) ) {
998  $html = $icon;
999  } else { // Assuming array
1000  $url = $icon["url"] ?? null;
1001  unset( $icon["url"] );
1002  if ( isset( $icon["src"] ) && $withImage === 'withImage' ) {
1003  // Lazy-load footer icons, since they're not part of the printed view.
1004  $icon["loading"] = 'lazy';
1005  // do this the lazy way, just pass icon data as an attribute array
1006  $html = Html::element( 'img', $icon );
1007  } else {
1008  $html = htmlspecialchars( $icon["alt"] );
1009  }
1010  if ( $url ) {
1011  $html = Html::rawElement( 'a',
1012  [ "href" => $url, "target" => $this->getConfig()->get( 'ExternalLinkTarget' ) ],
1013  $html );
1014  }
1015  }
1016  return $html;
1017  }
1018 
1023  public function mainPageLink() {
1024  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1025  $s = $linkRenderer->makeKnownLink(
1027  $this->msg( 'mainpage' )->text()
1028  );
1029 
1030  return $s;
1031  }
1032 
1039  public function footerLink( $desc, $page ) {
1040  $title = $this->footerLinkTitle( $desc, $page );
1041  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1042  if ( !$title ) {
1043  return '';
1044  }
1045 
1046  return $linkRenderer->makeKnownLink(
1047  $title,
1048  $this->msg( $desc )->text()
1049  );
1050  }
1051 
1057  private function footerLinkTitle( $desc, $page ) {
1058  // If the link description has been set to "-" in the default language,
1059  if ( $this->msg( $desc )->inContentLanguage()->isDisabled() ) {
1060  // then it is disabled, for all languages.
1061  return null;
1062  }
1063  // Otherwise, we display the link for the user, described in their
1064  // language (which may or may not be the same as the default language),
1065  // but we make the link target be the one site-wide page.
1066  $title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() );
1067 
1068  return $title ?: null;
1069  }
1070 
1077  public function getSiteFooterLinks() {
1078  $callback = function () {
1079  return [
1080  'privacy' => $this->privacyLink(),
1081  'about' => $this->aboutLink(),
1082  'disclaimer' => $this->disclaimerLink()
1083  ];
1084  };
1085 
1086  $services = MediaWikiServices::getInstance();
1087  $msgCache = $services->getMessageCache();
1088  $wanCache = $services->getMainWANObjectCache();
1089  $config = $this->getConfig();
1090 
1091  return ( $config->get( 'FooterLinkCacheExpiry' ) > 0 )
1092  ? $wanCache->getWithSetCallback(
1093  $wanCache->makeKey( 'footer-links' ),
1094  $config->get( 'FooterLinkCacheExpiry' ),
1095  $callback,
1096  [
1097  'checkKeys' => [
1098  // Unless there is both no exact $code override nor an i18n definition
1099  // in the software, the only MediaWiki page to check is for $code.
1100  $msgCache->getCheckKey( $this->getLanguage()->getCode() )
1101  ],
1102  'lockTSE' => 30
1103  ]
1104  )
1105  : $callback();
1106  }
1107 
1112  public function privacyLink() {
1113  return $this->footerLink( 'privacy', 'privacypage' );
1114  }
1115 
1120  public function aboutLink() {
1121  return $this->footerLink( 'aboutsite', 'aboutpage' );
1122  }
1123 
1128  public function disclaimerLink() {
1129  return $this->footerLink( 'disclaimers', 'disclaimerpage' );
1130  }
1131 
1139  public function editUrlOptions() {
1140  $options = [ 'action' => 'edit' ];
1141  $out = $this->getOutput();
1142 
1143  if ( !$out->isRevisionCurrent() ) {
1144  $options['oldid'] = intval( $out->getRevisionId() );
1145  }
1146 
1147  return $options;
1148  }
1149 
1154  public function showEmailUser( $id ) {
1155  if ( $id instanceof User ) {
1156  $targetUser = $id;
1157  } else {
1158  $targetUser = User::newFromId( $id );
1159  }
1160 
1161  # The sending user must have a confirmed email address and the receiving
1162  # user must accept emails from the sender.
1163  return $this->getUser()->canSendEmail()
1164  && SpecialEmailUser::validateTarget( $targetUser, $this->getUser() ) === '';
1165  }
1166 
1178  public function getSkinStylePath( $name ) {
1179  if ( $this->stylename === null ) {
1180  $class = static::class;
1181  throw new MWException( "$class::\$stylename must be set to use getSkinStylePath()" );
1182  }
1183 
1184  return $this->getConfig()->get( 'StylePath' ) . "/{$this->stylename}/$name";
1185  }
1186 
1187  /* these are used extensively in SkinTemplate, but also some other places */
1188 
1193  public static function makeMainPageUrl( $urlaction = '' ) {
1195  self::checkTitle( $title, '' );
1196 
1197  return $title->getLinkURL( $urlaction );
1198  }
1199 
1211  public static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) {
1213  if ( $proto === null ) {
1214  return $title->getLocalURL( $urlaction );
1215  } else {
1216  return $title->getFullURL( $urlaction, false, $proto );
1217  }
1218  }
1219 
1226  public static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
1227  $title = SpecialPage::getSafeTitleFor( $name, $subpage );
1228  return $title->getLocalURL( $urlaction );
1229  }
1230 
1237  public static function makeI18nUrl( $name, $urlaction = '' ) {
1238  wfDeprecated( __METHOD__, '1.35' );
1239  $title = Title::newFromText( wfMessage( $name )->inContentLanguage()->text() );
1240  self::checkTitle( $title, $name );
1241  return $title->getLocalURL( $urlaction );
1242  }
1243 
1249  public static function makeUrl( $name, $urlaction = '' ) {
1250  $title = Title::newFromText( $name );
1251  self::checkTitle( $title, $name );
1252 
1253  return $title->getLocalURL( $urlaction );
1254  }
1255 
1262  public static function makeInternalOrExternalUrl( $name ) {
1263  if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $name ) ) {
1264  return $name;
1265  } else {
1266  return self::makeUrl( $name );
1267  }
1268  }
1269 
1278  public static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
1279  wfDeprecated( __METHOD__, '1.35' );
1280  $title = Title::makeTitleSafe( $namespace, $name );
1281  self::checkTitle( $title, $name );
1282 
1283  return $title->getLocalURL( $urlaction );
1284  }
1285 
1292  protected static function makeUrlDetails( $name, $urlaction = '' ) {
1293  $title = Title::newFromText( $name );
1294  self::checkTitle( $title, $name );
1295 
1296  return [
1297  'href' => $title->getLocalURL( $urlaction ),
1298  'exists' => $title->isKnown(),
1299  ];
1300  }
1301 
1308  protected static function makeKnownUrlDetails( $name, $urlaction = '' ) {
1309  $title = Title::newFromText( $name );
1310  self::checkTitle( $title, $name );
1311 
1312  return [
1313  'href' => $title->getLocalURL( $urlaction ),
1314  'exists' => true
1315  ];
1316  }
1317 
1324  public static function checkTitle( &$title, $name ) {
1325  if ( !is_object( $title ) ) {
1326  $title = Title::newFromText( $name );
1327  if ( !is_object( $title ) ) {
1328  $title = Title::newFromText( '--error: link target missing--' );
1329  }
1330  }
1331  }
1332 
1341  public function mapInterwikiToLanguage( $code ) {
1342  $map = $this->getConfig()->get( 'InterlanguageLinkCodeMap' );
1343  return $map[ $code ] ?? $code;
1344  }
1345 
1354  public function getLanguages() {
1355  if ( $this->getConfig()->get( 'HideInterlanguageLinks' ) ) {
1356  return [];
1357  }
1358  $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1359 
1360  $userLang = $this->getLanguage();
1361  $languageLinks = [];
1362  $langNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
1363 
1364  foreach ( $this->getOutput()->getLanguageLinks() as $languageLinkText ) {
1365  $class = 'interlanguage-link interwiki-' . explode( ':', $languageLinkText, 2 )[0];
1366 
1367  $languageLinkTitle = Title::newFromText( $languageLinkText );
1368  if ( !$languageLinkTitle ) {
1369  continue;
1370  }
1371 
1372  $ilInterwikiCode = $this->mapInterwikiToLanguage( $languageLinkTitle->getInterwiki() );
1373 
1374  $ilLangName = $langNameUtils->getLanguageName( $ilInterwikiCode );
1375 
1376  if ( strval( $ilLangName ) === '' ) {
1377  $ilDisplayTextMsg = $this->msg( "interlanguage-link-$ilInterwikiCode" );
1378  if ( !$ilDisplayTextMsg->isDisabled() ) {
1379  // Use custom MW message for the display text
1380  $ilLangName = $ilDisplayTextMsg->text();
1381  } else {
1382  // Last resort: fallback to the language link target
1383  $ilLangName = $languageLinkText;
1384  }
1385  } else {
1386  // Use the language autonym as display text
1387  $ilLangName = $this->getLanguage()->ucfirst( $ilLangName );
1388  }
1389 
1390  // CLDR extension or similar is required to localize the language name;
1391  // otherwise we'll end up with the autonym again.
1392  $ilLangLocalName = $langNameUtils->getLanguageName(
1393  $ilInterwikiCode,
1394  $userLang->getCode()
1395  );
1396 
1397  $languageLinkTitleText = $languageLinkTitle->getText();
1398  if ( $ilLangLocalName === '' ) {
1399  $ilFriendlySiteName = $this->msg( "interlanguage-link-sitename-$ilInterwikiCode" );
1400  if ( !$ilFriendlySiteName->isDisabled() ) {
1401  if ( $languageLinkTitleText === '' ) {
1402  $ilTitle = $this->msg(
1403  'interlanguage-link-title-nonlangonly',
1404  $ilFriendlySiteName->text()
1405  )->text();
1406  } else {
1407  $ilTitle = $this->msg(
1408  'interlanguage-link-title-nonlang',
1409  $languageLinkTitleText,
1410  $ilFriendlySiteName->text()
1411  )->text();
1412  }
1413  } else {
1414  // we have nothing friendly to put in the title, so fall back to
1415  // displaying the interlanguage link itself in the title text
1416  // (similar to what is done in page content)
1417  $ilTitle = $languageLinkTitle->getInterwiki() .
1418  ":$languageLinkTitleText";
1419  }
1420  } elseif ( $languageLinkTitleText === '' ) {
1421  $ilTitle = $this->msg(
1422  'interlanguage-link-title-langonly',
1423  $ilLangLocalName
1424  )->text();
1425  } else {
1426  $ilTitle = $this->msg(
1427  'interlanguage-link-title',
1428  $languageLinkTitleText,
1429  $ilLangLocalName
1430  )->text();
1431  }
1432 
1433  $ilInterwikiCodeBCP47 = LanguageCode::bcp47( $ilInterwikiCode );
1434  $languageLink = [
1435  'href' => $languageLinkTitle->getFullURL(),
1436  'text' => $ilLangName,
1437  'title' => $ilTitle,
1438  'class' => $class,
1439  'link-class' => 'interlanguage-link-target',
1440  'lang' => $ilInterwikiCodeBCP47,
1441  'hreflang' => $ilInterwikiCodeBCP47,
1442  ];
1443  $hookContainer->run(
1444  'SkinTemplateGetLanguageLink',
1445  [ &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() ],
1446  []
1447  );
1448  $languageLinks[] = $languageLink;
1449  }
1450 
1451  return $languageLinks;
1452  }
1453 
1460  protected function buildNavUrls() {
1461  $out = $this->getOutput();
1462  $request = $this->getRequest();
1463  $title = $this->getTitle();
1464  $thispage = $title->getPrefixedDBkey();
1465  $uploadNavigationUrl = $this->getConfig()->get( 'UploadNavigationUrl' );
1466 
1467  $nav_urls = [];
1468  $nav_urls['mainpage'] = [ 'href' => self::makeMainPageUrl() ];
1469  if ( $uploadNavigationUrl ) {
1470  $nav_urls['upload'] = [ 'href' => $uploadNavigationUrl ];
1471  } elseif ( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) {
1472  $nav_urls['upload'] = [ 'href' => self::makeSpecialUrl( 'Upload' ) ];
1473  } else {
1474  $nav_urls['upload'] = false;
1475  }
1476  $nav_urls['specialpages'] = [ 'href' => self::makeSpecialUrl( 'Specialpages' ) ];
1477 
1478  $nav_urls['print'] = false;
1479  $nav_urls['permalink'] = false;
1480  $nav_urls['info'] = false;
1481  $nav_urls['whatlinkshere'] = false;
1482  $nav_urls['recentchangeslinked'] = false;
1483  $nav_urls['contributions'] = false;
1484  $nav_urls['log'] = false;
1485  $nav_urls['blockip'] = false;
1486  $nav_urls['mute'] = false;
1487  $nav_urls['emailuser'] = false;
1488  $nav_urls['userrights'] = false;
1489 
1490  // A print stylesheet is attached to all pages, but nobody ever
1491  // figures that out. :) Add a link...
1492  if ( !$out->isPrintable() && ( $out->isArticle() || $title->isSpecialPage() ) ) {
1493  $nav_urls['print'] = [
1494  'text' => $this->msg( 'printableversion' )->text(),
1495  'href' => 'javascript:print();'
1496  ];
1497  }
1498 
1499  if ( $out->isArticle() ) {
1500  // Also add a "permalink" while we're at it
1501  $revid = $out->getRevisionId();
1502  if ( $revid ) {
1503  $nav_urls['permalink'] = [
1504  'text' => $this->msg( 'permalink' )->text(),
1505  'href' => $title->getLocalURL( "oldid=$revid" )
1506  ];
1507  }
1508  }
1509 
1510  if ( $out->isArticleRelated() ) {
1511  $nav_urls['whatlinkshere'] = [
1512  'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $thispage )->getLocalURL()
1513  ];
1514 
1515  $nav_urls['info'] = [
1516  'text' => $this->msg( 'pageinfo-toolboxlink' )->text(),
1517  'href' => $title->getLocalURL( "action=info" )
1518  ];
1519 
1520  if ( $title->exists() || $title->inNamespace( NS_CATEGORY ) ) {
1521  $nav_urls['recentchangeslinked'] = [
1522  'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $thispage )->getLocalURL()
1523  ];
1524  }
1525  }
1526 
1527  $user = $this->getRelevantUser();
1528  if ( $user ) {
1529  $rootUser = $user->getName();
1530 
1531  $nav_urls['contributions'] = [
1532  'text' => $this->msg( 'contributions', $rootUser )->text(),
1533  'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser ),
1534  'tooltip-params' => [ $rootUser ],
1535  ];
1536 
1537  $nav_urls['log'] = [
1538  'href' => self::makeSpecialUrlSubpage( 'Log', $rootUser )
1539  ];
1540 
1541  if (
1542  MediaWikiServices::getInstance()
1544  ->userHasRight( $this->getUser(), 'block' )
1545  ) {
1546  $nav_urls['blockip'] = [
1547  'text' => $this->msg( 'blockip', $rootUser )->text(),
1548  'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser )
1549  ];
1550  }
1551 
1552  if ( $this->showEmailUser( $user ) ) {
1553  $nav_urls['emailuser'] = [
1554  'text' => $this->msg( 'tool-link-emailuser', $rootUser )->text(),
1555  'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser ),
1556  'tooltip-params' => [ $rootUser ],
1557  ];
1558  }
1559 
1560  if ( !$user->isAnon() ) {
1561  if ( $this->getUser()->isRegistered() && $this->getConfig()->get( 'EnableSpecialMute' ) ) {
1562  $nav_urls['mute'] = [
1563  'text' => $this->msg( 'mute-preferences' )->text(),
1564  'href' => self::makeSpecialUrlSubpage( 'Mute', $rootUser )
1565  ];
1566  }
1567 
1568  $sur = new UserrightsPage;
1569  $sur->setContext( $this->getContext() );
1570  $canChange = $sur->userCanChangeRights( $user );
1571  $nav_urls['userrights'] = [
1572  'text' => $this->msg(
1573  $canChange ? 'tool-link-userrights' : 'tool-link-userrights-readonly',
1574  $rootUser
1575  )->text(),
1576  'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
1577  ];
1578  }
1579  }
1580 
1581  return $nav_urls;
1582  }
1583 
1589  final protected function buildFeedUrls() {
1590  $feeds = [];
1591  $out = $this->getOutput();
1592  if ( $out->isSyndicated() ) {
1593  foreach ( $out->getSyndicationLinks() as $format => $link ) {
1594  $feeds[$format] = [
1595  // Messages: feed-atom, feed-rss
1596  'text' => $this->msg( "feed-$format" )->text(),
1597  'href' => $link
1598  ];
1599  }
1600  }
1601  return $feeds;
1602  }
1603 
1629  public function buildSidebar() {
1630  $services = MediaWikiServices::getInstance();
1631  $callback = function ( $old = null, &$ttl = null ) {
1632  $bar = [];
1633  $this->addToSidebar( $bar, 'sidebar' );
1634  $this->getHookRunner()->onSkinBuildSidebar( $this, $bar );
1635  $msgCache = MediaWikiServices::getInstance()->getMessageCache();
1636  if ( $msgCache->isDisabled() ) {
1637  $ttl = WANObjectCache::TTL_UNCACHEABLE; // bug T133069
1638  }
1639 
1640  return $bar;
1641  };
1642 
1643  $msgCache = $services->getMessageCache();
1644  $wanCache = $services->getMainWANObjectCache();
1645  $config = $this->getConfig();
1646  $languageCode = $this->getLanguage()->getCode();
1647 
1648  $sidebar = $config->get( 'EnableSidebarCache' )
1649  ? $wanCache->getWithSetCallback(
1650  $wanCache->makeKey( 'sidebar', $languageCode ),
1651  $config->get( 'SidebarCacheExpiry' ),
1652  $callback,
1653  [
1654  'checkKeys' => [
1655  // Unless there is both no exact $code override nor an i18n definition
1656  // in the software, the only MediaWiki page to check is for $code.
1657  $msgCache->getCheckKey( $languageCode )
1658  ],
1659  'lockTSE' => 30
1660  ]
1661  )
1662  : $callback();
1663 
1664  $sidebar['TOOLBOX'] = $this->makeToolbox(
1665  $this->buildNavUrls(),
1666  $this->buildFeedUrls()
1667  );
1668  $sidebar['LANGUAGES'] = $this->getLanguages();
1669  // Apply post-processing to the cached value
1670  $this->getHookRunner()->onSidebarBeforeOutput( $this, $sidebar );
1671 
1672  return $sidebar;
1673  }
1674 
1684  public function addToSidebar( &$bar, $message ) {
1685  $this->addToSidebarPlain( $bar, $this->msg( $message )->inContentLanguage()->plain() );
1686  }
1687 
1695  public function addToSidebarPlain( &$bar, $text ) {
1696  $lines = explode( "\n", $text );
1697 
1698  $heading = '';
1699  $config = $this->getConfig();
1700  $messageTitle = $config->get( 'EnableSidebarCache' )
1701  ? Title::newMainPage() : $this->getTitle();
1702  $messageCache = MediaWikiServices::getInstance()->getMessageCache();
1703 
1704  foreach ( $lines as $line ) {
1705  if ( strpos( $line, '*' ) !== 0 ) {
1706  continue;
1707  }
1708  $line = rtrim( $line, "\r" ); // for Windows compat
1709 
1710  if ( strpos( $line, '**' ) !== 0 ) {
1711  $heading = trim( $line, '* ' );
1712  if ( !array_key_exists( $heading, $bar ) ) {
1713  $bar[$heading] = [];
1714  }
1715  } else {
1716  $line = trim( $line, '* ' );
1717 
1718  if ( strpos( $line, '|' ) !== false ) { // sanity check
1719  $line = $messageCache->transform( $line, false, null, $messageTitle );
1720  $line = array_map( 'trim', explode( '|', $line, 2 ) );
1721  if ( count( $line ) !== 2 ) {
1722  // Second sanity check, could be hit by people doing
1723  // funky stuff with parserfuncs... (T35321)
1724  continue;
1725  }
1726 
1727  $extraAttribs = [];
1728 
1729  $msgLink = $this->msg( $line[0] )->title( $messageTitle )->inContentLanguage();
1730  if ( $msgLink->exists() ) {
1731  $link = $msgLink->text();
1732  if ( $link == '-' ) {
1733  continue;
1734  }
1735  } else {
1736  $link = $line[0];
1737  }
1738  $msgText = $this->msg( $line[1] )->title( $messageTitle );
1739  if ( $msgText->exists() ) {
1740  $text = $msgText->text();
1741  } else {
1742  $text = $line[1];
1743  }
1744 
1745  if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $link ) ) {
1746  $href = $link;
1747 
1748  // Parser::getExternalLinkAttribs won't work here because of the Namespace things
1749  if ( $config->get( 'NoFollowLinks' ) &&
1750  !wfMatchesDomainList( $href, $config->get( 'NoFollowDomainExceptions' ) )
1751  ) {
1752  $extraAttribs['rel'] = 'nofollow';
1753  }
1754 
1755  if ( $config->get( 'ExternalLinkTarget' ) ) {
1756  $extraAttribs['target'] = $config->get( 'ExternalLinkTarget' );
1757  }
1758  } else {
1759  $title = Title::newFromText( $link );
1760 
1761  if ( $title ) {
1762  $title = $title->fixSpecialName();
1763  $href = $title->getLinkURL();
1764  } else {
1765  $href = 'INVALID-TITLE';
1766  }
1767  }
1768 
1769  $bar[$heading][] = array_merge( [
1770  'text' => $text,
1771  'href' => $href,
1772  'id' => Sanitizer::escapeIdForAttribute( 'n-' . strtr( $line[1], ' ', '-' ) ),
1773  'active' => false,
1774  ], $extraAttribs );
1775  } else {
1776  continue;
1777  }
1778  }
1779  }
1780 
1781  return $bar;
1782  }
1783 
1789  public function getNewtalks() {
1790  $newMessagesAlert = '';
1791  $user = $this->getUser();
1792  $services = MediaWikiServices::getInstance();
1793  $linkRenderer = $services->getLinkRenderer();
1794  $userHasNewMessages = $services->getTalkPageNotificationManager()
1795  ->userHasNewMessages( $user );
1796  $timestamp = $services->getTalkPageNotificationManager()
1797  ->getLatestSeenMessageTimestamp( $user );
1798  $newtalks = !$userHasNewMessages ? [] : [
1799  [
1800  // TODO: Deprecate adding wiki and link to array and redesign GetNewMessagesAlert hook
1801  'wiki' => WikiMap::getCurrentWikiId(),
1802  'link' => $user->getTalkPage()->getLocalURL(),
1803  'rev' => $timestamp ? $services->getRevisionLookup()
1804  ->getRevisionByTimestamp( $user->getTalkPage(), $timestamp ) : null
1805  ]
1806  ];
1807  $out = $this->getOutput();
1808 
1809  // Allow extensions to disable or modify the new messages alert
1810  if ( !$this->getHookRunner()->onGetNewMessagesAlert(
1811  $newMessagesAlert, $newtalks, $user, $out )
1812  ) {
1813  return '';
1814  }
1815  if ( $newMessagesAlert ) {
1816  return $newMessagesAlert;
1817  }
1818 
1819  if ( $newtalks !== [] ) {
1820  $uTalkTitle = $user->getTalkPage();
1821  $lastSeenRev = $newtalks[0]['rev'];
1822  $numAuthors = 0;
1823  if ( $lastSeenRev !== null ) {
1824  $plural = true; // Default if we have a last seen revision: if unknown, use plural
1825  $revStore = $services->getRevisionStore();
1826  $latestRev = $revStore->getRevisionByTitle(
1827  $uTalkTitle,
1828  0,
1829  RevisionLookup::READ_NORMAL
1830  );
1831  if ( $latestRev !== null ) {
1832  // Singular if only 1 unseen revision, plural if several unseen revisions.
1833  $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
1834  $numAuthors = $revStore->countAuthorsBetween(
1835  $uTalkTitle->getArticleID(),
1836  $lastSeenRev,
1837  $latestRev,
1838  null,
1839  10,
1840  'include_new'
1841  );
1842  }
1843  } else {
1844  // Singular if no revision -> diff link will show latest change only in any case
1845  $plural = false;
1846  }
1847  $plural = $plural ? 999 : 1;
1848  // 999 signifies "more than one revision". We don't know how many, and even if we did,
1849  // the number of revisions or authors is not necessarily the same as the number of
1850  // "messages".
1851  $newMessagesLink = $linkRenderer->makeKnownLink(
1852  $uTalkTitle,
1853  $this->msg( 'newmessageslinkplural' )->params( $plural )->text(),
1854  [],
1855  $uTalkTitle->isRedirect() ? [ 'redirect' => 'no' ] : []
1856  );
1857 
1858  $newMessagesDiffLink = $linkRenderer->makeKnownLink(
1859  $uTalkTitle,
1860  $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->text(),
1861  [],
1862  $lastSeenRev !== null
1863  ? [ 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' ]
1864  : [ 'diff' => 'cur' ]
1865  );
1866 
1867  if ( $numAuthors >= 1 && $numAuthors <= 10 ) {
1868  $newMessagesAlert = $this->msg(
1869  'youhavenewmessagesfromusers',
1870  $newMessagesLink,
1871  $newMessagesDiffLink
1872  )->numParams( $numAuthors, $plural );
1873  } else {
1874  // $numAuthors === 11 signifies "11 or more" ("more than 10")
1875  $newMessagesAlert = $this->msg(
1876  $numAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
1877  $newMessagesLink,
1878  $newMessagesDiffLink
1879  )->numParams( $plural );
1880  }
1881  $newMessagesAlert = $newMessagesAlert->text();
1882  // Disable CDN cache
1883  $out->setCdnMaxage( 0 );
1884  }
1885 
1886  return $newMessagesAlert;
1887  }
1888 
1896  private function getCachedNotice( $name ) {
1897  $config = $this->getConfig();
1898 
1899  if ( $name === 'default' ) {
1900  // special case
1901  $notice = $config->get( 'SiteNotice' );
1902  if ( empty( $notice ) ) {
1903  return false;
1904  }
1905  } else {
1906  $msg = $this->msg( $name )->inContentLanguage();
1907  if ( $msg->isBlank() ) {
1908  return '';
1909  } elseif ( $msg->isDisabled() ) {
1910  return false;
1911  }
1912  $notice = $msg->plain();
1913  }
1914 
1915  $services = MediaWikiServices::getInstance();
1916  $cache = $services->getMainWANObjectCache();
1917  $parsed = $cache->getWithSetCallback(
1918  // Use the extra hash appender to let eg SSL variants separately cache
1919  // Key is verified with md5 hash of unparsed wikitext
1920  $cache->makeKey( $name, $config->get( 'RenderHashAppend' ), md5( $notice ) ),
1921  // TTL in seconds
1922  600,
1923  function () use ( $notice ) {
1924  return $this->getOutput()->parseAsInterface( $notice );
1925  }
1926  );
1927 
1928  $contLang = $services->getContentLanguage();
1929  return Html::rawElement(
1930  'div',
1931  [
1932  'id' => 'localNotice',
1933  'lang' => $contLang->getHtmlCode(),
1934  'dir' => $contLang->getDir()
1935  ],
1936  $parsed
1937  );
1938  }
1939 
1945  public function getSiteNotice() {
1946  $siteNotice = '';
1947 
1948  if ( $this->getHookRunner()->onSiteNoticeBefore( $siteNotice, $this ) ) {
1949  if ( $this->getUser()->isLoggedIn() ) {
1950  $siteNotice = $this->getCachedNotice( 'sitenotice' );
1951  } else {
1952  $anonNotice = $this->getCachedNotice( 'anonnotice' );
1953  if ( $anonNotice === false ) {
1954  $siteNotice = $this->getCachedNotice( 'sitenotice' );
1955  } else {
1956  $siteNotice = $anonNotice;
1957  }
1958  }
1959  if ( $siteNotice === false ) {
1960  $siteNotice = $this->getCachedNotice( 'default' ) ?: '';
1961  }
1962  }
1963 
1964  $this->getHookRunner()->onSiteNoticeAfter( $siteNotice, $this );
1965  return $siteNotice;
1966  }
1967 
1981  public function doEditSectionLink( Title $nt, $section, $tooltip, Language $lang ) {
1982  // HTML generated here should probably have userlangattributes
1983  // added to it for LTR text on RTL pages
1984 
1985  $attribs = [];
1986  if ( $tooltip !== null ) {
1987  $attribs['title'] = $this->msg( 'editsectionhint' )->rawParams( $tooltip )
1988  ->inLanguage( $lang )->text();
1989  }
1990 
1991  $links = [
1992  'editsection' => [
1993  'text' => $this->msg( 'editsection' )->inLanguage( $lang )->text(),
1994  'targetTitle' => $nt,
1995  'attribs' => $attribs,
1996  'query' => [ 'action' => 'edit', 'section' => $section ]
1997  ]
1998  ];
1999 
2000  $this->getHookRunner()->onSkinEditSectionLinks( $this, $nt, $section, $tooltip, $links, $lang );
2001 
2002  $result = '<span class="mw-editsection"><span class="mw-editsection-bracket">[</span>';
2003 
2004  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2005  $linksHtml = [];
2006  foreach ( $links as $k => $linkDetails ) {
2007  $linksHtml[] = $linkRenderer->makeKnownLink(
2008  $linkDetails['targetTitle'],
2009  $linkDetails['text'],
2010  $linkDetails['attribs'],
2011  $linkDetails['query']
2012  );
2013  }
2014 
2015  $result .= implode(
2016  '<span class="mw-editsection-divider">'
2017  . $this->msg( 'pipe-separator' )->inLanguage( $lang )->escaped()
2018  . '</span>',
2019  $linksHtml
2020  );
2021 
2022  $result .= '<span class="mw-editsection-bracket">]</span></span>';
2023  return $result;
2024  }
2025 
2035  public function makeToolbox( $navUrls, $feedUrls ) {
2036  $toolbox = [];
2037  if ( $navUrls['whatlinkshere'] ?? null ) {
2038  $toolbox['whatlinkshere'] = $navUrls['whatlinkshere'];
2039  $toolbox['whatlinkshere']['id'] = 't-whatlinkshere';
2040  }
2041  if ( $navUrls['recentchangeslinked'] ?? null ) {
2042  $toolbox['recentchangeslinked'] = $navUrls['recentchangeslinked'];
2043  $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
2044  $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
2045  $toolbox['recentchangeslinked']['rel'] = 'nofollow';
2046  }
2047  if ( $feedUrls ?? null ) {
2048  $toolbox['feeds']['id'] = 'feedlinks';
2049  $toolbox['feeds']['links'] = [];
2050  foreach ( $feedUrls as $key => $feed ) {
2051  $toolbox['feeds']['links'][$key] = $feed;
2052  $toolbox['feeds']['links'][$key]['id'] = "feed-$key";
2053  $toolbox['feeds']['links'][$key]['rel'] = 'alternate';
2054  $toolbox['feeds']['links'][$key]['type'] = "application/{$key}+xml";
2055  $toolbox['feeds']['links'][$key]['class'] = 'feedlink';
2056  }
2057  }
2058  foreach ( [ 'contributions', 'log', 'blockip', 'emailuser', 'mute',
2059  'userrights', 'upload', 'specialpages' ] as $special
2060  ) {
2061  if ( $navUrls[$special] ?? null ) {
2062  $toolbox[$special] = $navUrls[$special];
2063  $toolbox[$special]['id'] = "t-$special";
2064  }
2065  }
2066  if ( $navUrls['print'] ?? null ) {
2067  $toolbox['print'] = $navUrls['print'];
2068  $toolbox['print']['id'] = 't-print';
2069  $toolbox['print']['rel'] = 'alternate';
2070  $toolbox['print']['msg'] = 'printableversion';
2071  }
2072  if ( $navUrls['permalink'] ?? null ) {
2073  $toolbox['permalink'] = $navUrls['permalink'];
2074  $toolbox['permalink']['id'] = 't-permalink';
2075  }
2076  if ( $navUrls['info'] ?? null ) {
2077  $toolbox['info'] = $navUrls['info'];
2078  $toolbox['info']['id'] = 't-info';
2079  }
2080 
2081  return $toolbox;
2082  }
2083 
2101  final public function getIndicatorsHTML( $indicators ) {
2102  wfDeprecated( __METHOD__, '1.36' );
2103 
2104  $out = "<div class=\"mw-indicators mw-body-content\">\n";
2105  foreach ( $this->getIndicatorsData( $indicators ) as $indicatorData ) {
2106  $out .= Html::rawElement(
2107  'div',
2108  [
2109  'id' => $indicatorData['id'],
2110  'class' => $indicatorData['class']
2111  ],
2112  $indicatorData['html']
2113  ) . "\n";
2114  }
2115  $out .= "</div>\n";
2116  return $out;
2117  }
2118 
2125  protected function getIndicatorsData( $indicators ) {
2126  $indicatorData = [];
2127  foreach ( $indicators as $id => $content ) {
2128  $indicatorData[] = [
2129  'id' => Sanitizer::escapeIdForAttribute( "mw-indicator-$id" ),
2130  'class' => 'mw-indicator',
2131  'html' => $content,
2132  ];
2133  }
2134  return $indicatorData;
2135  }
2136 
2149  final public function getPersonalToolsForMakeListItem( $urls ) {
2150  $personal_tools = [];
2151  foreach ( $urls as $key => $plink ) {
2152  # The class on a personal_urls item is meant to go on the <a> instead
2153  # of the <li> so we have to use a single item "links" array instead
2154  # of using most of the personal_url's keys directly.
2155  $ptool = [
2156  'links' => [
2157  [ 'single-id' => "pt-$key" ],
2158  ],
2159  'id' => "pt-$key",
2160  ];
2161  if ( isset( $plink['active'] ) ) {
2162  $ptool['active'] = $plink['active'];
2163  }
2164  foreach ( [
2165  'href',
2166  'class',
2167  'text',
2168  'dir',
2169  'data',
2170  'exists',
2171  'data-mw'
2172  ] as $k ) {
2173  if ( isset( $plink[$k] ) ) {
2174  $ptool['links'][0][$k] = $plink[$k];
2175  }
2176  }
2177  $personal_tools[$key] = $ptool;
2178  }
2179  return $personal_tools;
2180  }
2181 
2235  final public function makeLink( $key, $item, $options = [] ) {
2236  $text = $item['text'] ?? $this->msg( $item['msg'] ?? $key )->text();
2237 
2238  $html = htmlspecialchars( $text );
2239 
2240  if ( isset( $options['text-wrapper'] ) ) {
2241  $wrapper = $options['text-wrapper'];
2242  if ( isset( $wrapper['tag'] ) ) {
2243  $wrapper = [ $wrapper ];
2244  }
2245  while ( count( $wrapper ) > 0 ) {
2246  $element = array_pop( $wrapper );
2247  $html = Html::rawElement( $element['tag'], $element['attributes'] ?? null, $html );
2248  }
2249  }
2250 
2251  if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
2252  $attrs = $item;
2253  foreach ( [ 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary',
2254  'tooltip-params', 'exists' ] as $k ) {
2255  unset( $attrs[$k] );
2256  }
2257 
2258  if ( isset( $attrs['data'] ) ) {
2259  foreach ( $attrs['data'] as $key => $value ) {
2260  $attrs[ 'data-' . $key ] = $value;
2261  }
2262  unset( $attrs[ 'data' ] );
2263  }
2264 
2265  if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
2266  $item['single-id'] = $item['id'];
2267  }
2268 
2269  $tooltipParams = [];
2270  if ( isset( $item['tooltip-params'] ) ) {
2271  $tooltipParams = $item['tooltip-params'];
2272  }
2273 
2274  if ( isset( $item['single-id'] ) ) {
2275  $tooltipOption = isset( $item['exists'] ) && $item['exists'] === false ? 'nonexisting' : null;
2276 
2277  if ( isset( $item['tooltiponly'] ) && $item['tooltiponly'] ) {
2278  $title = Linker::titleAttrib( $item['single-id'], $tooltipOption, $tooltipParams );
2279  if ( $title !== false ) {
2280  $attrs['title'] = $title;
2281  }
2282  } else {
2284  $item['single-id'],
2285  $tooltipParams,
2286  $tooltipOption
2287  );
2288  if ( isset( $tip['title'] ) && $tip['title'] !== false ) {
2289  $attrs['title'] = $tip['title'];
2290  }
2291  if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) {
2292  $attrs['accesskey'] = $tip['accesskey'];
2293  }
2294  }
2295  }
2296  if ( isset( $options['link-class'] ) ) {
2297  if ( isset( $attrs['class'] ) ) {
2298  $attrs['class'] .= " {$options['link-class']}";
2299  } else {
2300  $attrs['class'] = $options['link-class'];
2301  }
2302  }
2303  $html = Html::rawElement( isset( $attrs['href'] )
2304  ? 'a'
2305  : $options['link-fallback'], $attrs, $html );
2306  }
2307 
2308  return $html;
2309  }
2310 
2343  final public function makeListItem( $key, $item, $options = [] ) {
2344  // In case this is still set from SkinTemplate, we don't want it to appear in
2345  // the HTML output (normally removed in SkinTemplate::buildContentActionUrls())
2346  unset( $item['redundant'] );
2347 
2348  if ( isset( $item['links'] ) ) {
2349  $links = [];
2350  foreach ( $item['links'] as $linkKey => $link ) {
2351  $links[] = $this->makeLink( $linkKey, $link, $options );
2352  }
2353  $html = implode( ' ', $links );
2354  } else {
2355  $link = $item;
2356  // These keys are used by makeListItem and shouldn't be passed on to the link
2357  foreach ( [ 'id', 'class', 'active', 'tag', 'itemtitle' ] as $k ) {
2358  unset( $link[$k] );
2359  }
2360  if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
2361  // The id goes on the <li> not on the <a> for single links
2362  // but makeSidebarLink still needs to know what id to use when
2363  // generating tooltips and accesskeys.
2364  $link['single-id'] = $item['id'];
2365  }
2366  if ( isset( $link['link-class'] ) ) {
2367  // link-class should be set on the <a> itself,
2368  // so pass it in as 'class'
2369  $link['class'] = $link['link-class'];
2370  unset( $link['link-class'] );
2371  }
2372  $html = $this->makeLink( $key, $link, $options );
2373  }
2374 
2375  $attrs = [];
2376  foreach ( [ 'id', 'class' ] as $attr ) {
2377  if ( isset( $item[$attr] ) ) {
2378  $attrs[$attr] = $item[$attr];
2379  }
2380  }
2381  if ( isset( $item['active'] ) && $item['active'] ) {
2382  if ( !isset( $attrs['class'] ) ) {
2383  $attrs['class'] = '';
2384  }
2385  $attrs['class'] .= ' active';
2386  $attrs['class'] = trim( $attrs['class'] );
2387  }
2388  if ( isset( $item['itemtitle'] ) ) {
2389  $attrs['title'] = $item['itemtitle'];
2390  }
2391  return Html::rawElement( $options['tag'] ?? 'li', $attrs, $html );
2392  }
2393 
2400  final public function makeSearchInput( $attrs = [] ) {
2401  $realAttrs = [
2402  'type' => 'search',
2403  'name' => 'search',
2404  'placeholder' => $this->msg( 'searchsuggest-search' )->text(),
2405  ];
2406  $realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
2407  return Html::element( 'input', $realAttrs );
2408  }
2409 
2417  final public function makeSearchButton( $mode, $attrs = [] ) {
2418  switch ( $mode ) {
2419  case 'go':
2420  case 'fulltext':
2421  $realAttrs = [
2422  'type' => 'submit',
2423  'name' => $mode,
2424  'value' => $this->msg( $mode == 'go' ? 'searcharticle' : 'searchbutton' )->text(),
2425  ];
2426  $realAttrs = array_merge(
2427  $realAttrs,
2428  Linker::tooltipAndAccesskeyAttribs( "search-$mode" ),
2429  $attrs
2430  );
2431  return Html::element( 'input', $realAttrs );
2432  case 'image':
2433  $buttonAttrs = [
2434  'type' => 'submit',
2435  'name' => 'button',
2436  ];
2437  $buttonAttrs = array_merge(
2438  $buttonAttrs,
2439  Linker::tooltipAndAccesskeyAttribs( 'search-fulltext' ),
2440  $attrs
2441  );
2442  unset( $buttonAttrs['src'] );
2443  unset( $buttonAttrs['alt'] );
2444  unset( $buttonAttrs['width'] );
2445  unset( $buttonAttrs['height'] );
2446  $imgAttrs = [
2447  'src' => $attrs['src'],
2448  'alt' => $attrs['alt'] ?? $this->msg( 'searchbutton' )->text(),
2449  'width' => $attrs['width'] ?? null,
2450  'height' => $attrs['height'] ?? null,
2451  ];
2452  return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
2453  default:
2454  throw new MWException( 'Unknown mode passed to BaseTemplate::makeSearchButton' );
2455  }
2456  }
2457 
2468  public function getAfterPortlet( string $name ) : string {
2469  $html = '';
2470 
2471  $this->getHookRunner()->onSkinAfterPortlet( $this, $name, $html );
2472 
2473  return $html;
2474  }
2475 }
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:47
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:67
Skin\editUrlOptions
editUrlOptions()
Return URL options for the 'edit page' link.
Definition: Skin.php:1139
Skin\showEmailUser
showEmailUser( $id)
Definition: Skin.php:1154
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:562
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:329
Skin\makeUrlDetails
static makeUrlDetails( $name, $urlaction='')
these return an array with the 'href' and boolean 'exists'
Definition: Skin.php:1292
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:42
Skin\getSiteNotice
getSiteNotice()
Get the site notice.
Definition: Skin.php:1945
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:1589
Skin\$options
array $options
Skin options passed into constructor.
Definition: Skin.php:52
Skin\footerLinkTitle
footerLinkTitle( $desc, $page)
Definition: Skin.php:1057
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:919
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:154
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
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:1072
true
return true
Definition: router.php:90
Skin\makeVariablesScript
static makeVariablesScript( $data, $nonce=null)
Definition: Skin.php:439
$fallback
$fallback
Definition: MessagesAb.php:11
Skin\lastModified
lastModified()
Get the timestamp of the latest revision, formatted in user language.
Definition: Skin.php:942
Skin\getSiteFooterLinks
getSiteFooterLinks()
Gets the link to the wiki's privacy policy, about page, and disclaimer page.
Definition: Skin.php:1077
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:1324
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:1341
Skin\makeSpecialUrl
static makeSpecialUrl( $name, $urlaction='', $proto=null)
Make a URL for a Special Page using the given query and protocol.
Definition: Skin.php:1211
Skin\getCategoryLinks
getCategoryLinks()
Definition: Skin.php:523
Skin\aboutLink
aboutLink()
Gets the link to the wiki's about page.
Definition: Skin.php:1120
Skin\makeSpecialUrlSubpage
static makeSpecialUrlSubpage( $name, $subpage, $urlaction='')
Definition: Skin.php:1226
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:538
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1219
$s
$s
Definition: mergeMessageFileList.php:185
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:1216
ContextSource\getRequest
getRequest()
Definition: ContextSource.php:76
Title\newMainPage
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:654
Skin\$mRelevantUser
$mRelevantUser
Definition: Skin.php:54
ContextSource\getUser
getUser()
Stable to override.
Definition: ContextSource.php:131
ContextSource\getTitle
getTitle()
Definition: ContextSource.php:85
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:859
WikiMap\getCurrentWikiId
static getCurrentWikiId()
Definition: WikiMap.php:303
Skin\makeI18nUrl
static makeI18nUrl( $name, $urlaction='')
Definition: Skin.php:1237
Skin\getHtmlElementAttributes
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:502
SpecialEmailUser\validateTarget
static validateTarget( $target, User $sender)
Validate target User.
Definition: SpecialEmailUser.php:193
Skin\doEditSectionLink
doEditSectionLink(Title $nt, $section, $tooltip, Language $lang)
Create a section edit link.
Definition: Skin.php:1981
Skin\makeListItem
makeListItem( $key, $item, $options=[])
Generates a list item for a navigation, portlet, portal, sidebar...
Definition: Skin.php:2343
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:690
Skin\mainPageLink
mainPageLink()
Gets the link to the wiki's main page.
Definition: Skin.php:1023
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:66
Skin\afterContentHook
afterContentHook()
This runs a hook to allow extensions placing their stuff after content and article metadata (e....
Definition: Skin.php:652
ContextSource\getLanguage
getLanguage()
Definition: ContextSource.php:140
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:1896
UserrightsPage
Special page to allow managing user group membership.
Definition: SpecialUserrights.php:31
MWException
MediaWiki exception.
Definition: MWException.php:29
Skin\getIndicatorsData
getIndicatorsData( $indicators)
Return an array of indicator data.
Definition: Skin.php:2125
Skin\buildSidebar
buildSidebar()
Build an array that represents the sidebar(s), the navigation bar among them.
Definition: Skin.php:1629
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1026
Skin\privacyLink
privacyLink()
Gets the link to the wiki's privacy policy page.
Definition: Skin.php:1112
Skin\normalizeKey
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition: Skin.php:103
Skin\footerLink
footerLink( $desc, $page)
Returns an HTML link for use in the footer.
Definition: Skin.php:1039
Skin\generateDebugHTML
generateDebugHTML()
Generate debug data HTML for displaying at the bottom of the main content area.
Definition: Skin.php:680
getPermissionManager
getPermissionManager()
$wgFallbackSkin
$wgFallbackSkin
Fallback skin used when the skin defined by $wgDefaultSkin can't be found.
Definition: DefaultSettings.php:3460
Skin\$stylename
string $stylename
Stylesheets set to use.
Definition: Skin.php:60
Skin\getCategories
getCategories()
Definition: Skin.php:619
Skin\setRelevantUser
setRelevantUser( $u)
Set the "relevant" user.
Definition: Skin.php:392
ContextSource\getOutput
getOutput()
Definition: ContextSource.php:121
ContextSource
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
Definition: ContextSource.php:30
$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:1684
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:948
Skin\isRevisionCurrent
isRevisionCurrent()
Whether the revision displayed is the latest revision of the page.
Definition: Skin.php:358
Skin\drawCategoryBrowser
drawCategoryBrowser( $tree)
Render the array as a series of links.
Definition: Skin.php:595
Skin\getLanguages
getLanguages()
Generates array of language links for the current page.
Definition: Skin.php:1354
Skin\makeNSUrl
static makeNSUrl( $name, $urlaction='', $namespace=NS_MAIN)
this can be passed the NS number as defined in Language.php
Definition: Skin.php:1278
$title
$title
Definition: testCompression.php:38
Skin\preloadExistence
preloadExistence()
Preload the existence of three commonly-requested pages in a single query.
Definition: Skin.php:298
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:3453
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:1128
Skin\getLogo
getLogo()
URL to the default square logo (1x key) Please use ResourceLoaderSkinModule::getAvailableLogos.
Definition: Skin.php:516
$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:909
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:2035
ResourceLoaderSkinModule\getAvailableLogos
static getAvailableLogos( $conf)
Return an array of all available logos that a skin may use.
Definition: ResourceLoaderSkinModule.php:329
Skin\$mRelevantTitle
$mRelevantTitle
Definition: Skin.php:53
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:184
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:618
Skin\getRelevantTitle
getRelevantTitle()
Return the "relevant" title.
Definition: Skin.php:383
$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:1178
Skin\getPageClasses
getPageClasses( $title)
TODO: document.
Definition: Skin.php:464
Skin\makeSearchInput
makeSearchInput( $attrs=[])
Definition: Skin.php:2400
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:2101
Skin\getSearchLink
getSearchLink()
Definition: Skin.php:837
$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:2149
Skin\printSource
printSource()
Text with the permalink to the source page, usually shown on the footer of a printed page.
Definition: Skin.php:713
Skin\buildNavUrls
buildNavUrls()
Build array of common navigation links.
Definition: Skin.php:1460
Skin\getAfterPortlet
getAfterPortlet(string $name)
Allows extensions to hook into known portlets and add stuff to them.
Definition: Skin.php:2468
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:973
Skin\getNewtalks
getNewtalks()
Gets new talk page messages for the current user and returns an appropriate alert message (or an empt...
Definition: Skin.php:1789
Title
Represents a title within MediaWiki.
Definition: Title.php:42
Skin\makeUrl
static makeUrl( $name, $urlaction='')
Definition: Skin.php:1249
wfMatchesDomainList
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
Definition: GlobalFunctions.php:875
$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:846
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:404
Skin\subPageSubtitle
subPageSubtitle( $out=null)
Definition: Skin.php:780
NS_USER
const NS_USER
Definition: Defines.php:71
Skin\getUndeleteLink
getUndeleteLink()
Definition: Skin.php:732
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:1308
$wgSkipSkins
$wgSkipSkins
Specify the names of skins that should not be presented in the list of available skins in user prefer...
Definition: DefaultSettings.php:3472
Skin\setupSkinUserCss
setupSkinUserCss(OutputPage $out)
Hook point for adding style modules to OutputPage.
Definition: Skin.php:455
Skin\makeMainPageUrl
static makeMainPageUrl( $urlaction='')
Definition: Skin.php:1193
$t
$t
Definition: testCompression.php:74
Skin\getCopyrightIcon
getCopyrightIcon()
Definition: Skin.php:889
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:78
Skin
The main skin class which provides methods and properties for all other skins.
Definition: Skin.php:41
Skin\initPage
initPage(OutputPage $out)
Stable to override.
Definition: Skin.php:190
Skin\setRelevantTitle
setRelevantTitle( $t)
Set the "relevant" title.
Definition: Skin.php:369
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:55
Skin\isResponsive
isResponsive()
Indicates if this skin is responsive.
Definition: Skin.php:182
Skin\makeSearchButton
makeSearchButton( $mode, $attrs=[])
Definition: Skin.php:2417
Skin\getRevisionId
getRevisionId()
Get the current revision ID.
Definition: Skin.php:346
Skin\addToSidebarPlain
addToSidebarPlain(&$bar, $text)
Add content from plain text.
Definition: Skin.php:1695
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:2235
Skin\makeInternalOrExternalUrl
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition: Skin.php:1262
Skin\makeFooterIcon
makeFooterIcon( $icon, $withImage='withImage')
Renders a $wgFooterIcons icon according to the method's arguments.
Definition: Skin.php:996
$type
$type
Definition: testCompression.php:52