MediaWiki  master
SkinTemplate.php
Go to the documentation of this file.
1 <?php
23 
38 class SkinTemplate extends Skin {
43  public $skinname = 'monobook';
44 
49  public $template = QuickTemplate::class;
50 
51  public $thispage;
52  public $titletxt;
53  public $userpage;
54  public $thisquery;
55  public $loggedin;
56  public $username;
58 
68  function setupTemplate( $classname ) {
69  return new $classname( $this->getConfig() );
70  }
71 
77  public function getLanguages() {
79  if ( $wgHideInterlanguageLinks ) {
80  return [];
81  }
82 
83  $userLang = $this->getLanguage();
84  $languageLinks = [];
85 
86  foreach ( $this->getOutput()->getLanguageLinks() as $languageLinkText ) {
87  $class = 'interlanguage-link interwiki-' . explode( ':', $languageLinkText, 2 )[0];
88 
89  $languageLinkTitle = Title::newFromText( $languageLinkText );
90  if ( !$languageLinkTitle ) {
91  continue;
92  }
93 
94  $ilInterwikiCode = $this->mapInterwikiToLanguage( $languageLinkTitle->getInterwiki() );
95 
96  $ilLangName = Language::fetchLanguageName( $ilInterwikiCode );
97 
98  if ( strval( $ilLangName ) === '' ) {
99  $ilDisplayTextMsg = wfMessage( "interlanguage-link-$ilInterwikiCode" );
100  if ( !$ilDisplayTextMsg->isDisabled() ) {
101  // Use custom MW message for the display text
102  $ilLangName = $ilDisplayTextMsg->text();
103  } else {
104  // Last resort: fallback to the language link target
105  $ilLangName = $languageLinkText;
106  }
107  } else {
108  // Use the language autonym as display text
109  $ilLangName = $this->formatLanguageName( $ilLangName );
110  }
111 
112  // CLDR extension or similar is required to localize the language name;
113  // otherwise we'll end up with the autonym again.
114  $ilLangLocalName = Language::fetchLanguageName(
115  $ilInterwikiCode,
116  $userLang->getCode()
117  );
118 
119  $languageLinkTitleText = $languageLinkTitle->getText();
120  if ( $ilLangLocalName === '' ) {
121  $ilFriendlySiteName = wfMessage( "interlanguage-link-sitename-$ilInterwikiCode" );
122  if ( !$ilFriendlySiteName->isDisabled() ) {
123  if ( $languageLinkTitleText === '' ) {
124  $ilTitle = wfMessage(
125  'interlanguage-link-title-nonlangonly',
126  $ilFriendlySiteName->text()
127  )->text();
128  } else {
129  $ilTitle = wfMessage(
130  'interlanguage-link-title-nonlang',
131  $languageLinkTitleText,
132  $ilFriendlySiteName->text()
133  )->text();
134  }
135  } else {
136  // we have nothing friendly to put in the title, so fall back to
137  // displaying the interlanguage link itself in the title text
138  // (similar to what is done in page content)
139  $ilTitle = $languageLinkTitle->getInterwiki() .
140  ":$languageLinkTitleText";
141  }
142  } elseif ( $languageLinkTitleText === '' ) {
143  $ilTitle = wfMessage(
144  'interlanguage-link-title-langonly',
145  $ilLangLocalName
146  )->text();
147  } else {
148  $ilTitle = wfMessage(
149  'interlanguage-link-title',
150  $languageLinkTitleText,
151  $ilLangLocalName
152  )->text();
153  }
154 
155  $ilInterwikiCodeBCP47 = LanguageCode::bcp47( $ilInterwikiCode );
156  $languageLink = [
157  'href' => $languageLinkTitle->getFullURL(),
158  'text' => $ilLangName,
159  'title' => $ilTitle,
160  'class' => $class,
161  'link-class' => 'interlanguage-link-target',
162  'lang' => $ilInterwikiCodeBCP47,
163  'hreflang' => $ilInterwikiCodeBCP47,
164  ];
165  Hooks::run(
166  'SkinTemplateGetLanguageLink',
167  [ &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() ]
168  );
169  $languageLinks[] = $languageLink;
170  }
171  return $languageLinks;
172  }
173 
177  protected function setupTemplateForOutput() {
178  $request = $this->getRequest();
179  $user = $this->getUser();
180  $title = $this->getTitle();
181 
182  $tpl = $this->setupTemplate( $this->template );
183 
184  $this->thispage = $title->getPrefixedDBkey();
185  $this->titletxt = $title->getPrefixedText();
186  $this->userpage = $user->getUserPage()->getPrefixedText();
187  $query = [];
188  if ( !$request->wasPosted() ) {
189  $query = $request->getValues();
190  unset( $query['title'] );
191  unset( $query['returnto'] );
192  unset( $query['returntoquery'] );
193  }
194  $this->thisquery = wfArrayToCgi( $query );
195  $this->loggedin = $user->isLoggedIn();
196  $this->username = $user->getName();
197 
198  if ( $this->loggedin ) {
199  $this->userpageUrlDetails = self::makeUrlDetails( $this->userpage );
200  } else {
201  # This won't be used in the standard skins, but we define it to preserve the interface
202  # To save time, we check for existence
203  $this->userpageUrlDetails = self::makeKnownUrlDetails( $this->userpage );
204  }
205 
206  return $tpl;
207  }
208 
212  function outputPage() {
213  Profiler::instance()->setAllowOutput();
214  $out = $this->getOutput();
215 
216  $this->initPage( $out );
217  $tpl = $this->prepareQuickTemplate();
218  // execute template
219  $res = $tpl->execute();
220 
221  // result may be an error
222  $this->printOrError( $res );
223  }
224 
232  protected function wrapHTML( $title, $html ) {
233  # An ID that includes the actual body text; without categories, contentSub, ...
234  $realBodyAttribs = [ 'id' => 'mw-content-text' ];
235 
236  # Add a mw-content-ltr/rtl class to be able to style based on text
237  # direction when the content is different from the UI language (only
238  # when viewing)
239  # Most information on special pages and file pages is in user language,
240  # rather than content language, so those will not get this
241  if ( Action::getActionName( $this ) === 'view' &&
242  ( !$title->inNamespaces( NS_SPECIAL, NS_FILE ) || $title->isRedirect() ) ) {
243  $pageLang = $title->getPageViewLanguage();
244  $realBodyAttribs['lang'] = $pageLang->getHtmlCode();
245  $realBodyAttribs['dir'] = $pageLang->getDir();
246  $realBodyAttribs['class'] = 'mw-content-' . $pageLang->getDir();
247  }
248 
249  return Html::rawElement( 'div', $realBodyAttribs, $html );
250  }
251 
258  protected function prepareQuickTemplate() {
263 
264  $title = $this->getTitle();
265  $request = $this->getRequest();
266  $out = $this->getOutput();
267  $tpl = $this->setupTemplateForOutput();
268 
269  $tpl->set( 'title', $out->getPageTitle() );
270  $tpl->set( 'pagetitle', $out->getHTMLTitle() );
271  $tpl->set( 'displaytitle', $out->mPageLinkTitle );
272 
273  $tpl->set( 'thispage', $this->thispage );
274  $tpl->set( 'titleprefixeddbkey', $this->thispage );
275  $tpl->set( 'titletext', $title->getText() );
276  $tpl->set( 'articleid', $title->getArticleID() );
277 
278  $tpl->set( 'isarticle', $out->isArticle() );
279 
280  $subpagestr = $this->subPageSubtitle();
281  if ( $subpagestr !== '' ) {
282  $subpagestr = '<span class="subpages">' . $subpagestr . '</span>';
283  }
284  $tpl->set( 'subtitle', $subpagestr . $out->getSubtitle() );
285 
286  $undelete = $this->getUndeleteLink();
287  if ( $undelete === '' ) {
288  $tpl->set( 'undelete', '' );
289  } else {
290  $tpl->set( 'undelete', '<span class="subpages">' . $undelete . '</span>' );
291  }
292 
293  $tpl->set( 'catlinks', $this->getCategories() );
294  if ( $out->isSyndicated() ) {
295  $feeds = [];
296  foreach ( $out->getSyndicationLinks() as $format => $link ) {
297  $feeds[$format] = [
298  // Messages: feed-atom, feed-rss
299  'text' => $this->msg( "feed-$format" )->text(),
300  'href' => $link
301  ];
302  }
303  $tpl->set( 'feeds', $feeds );
304  } else {
305  $tpl->set( 'feeds', false );
306  }
307 
308  $tpl->set( 'mimetype', $wgMimeType );
309  $tpl->set( 'charset', 'UTF-8' );
310  $tpl->set( 'wgScript', $wgScript );
311  $tpl->set( 'skinname', $this->skinname );
312  $tpl->set( 'skinclass', static::class );
313  $tpl->set( 'skin', $this );
314  $tpl->set( 'stylename', $this->stylename );
315  $tpl->set( 'printable', $out->isPrintable() );
316  $tpl->set( 'handheld', $request->getBool( 'handheld' ) );
317  $tpl->set( 'loggedin', $this->loggedin );
318  $tpl->set( 'notspecialpage', !$title->isSpecialPage() );
319  $tpl->set( 'searchaction', $this->getSearchLink() );
320  $tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBkey() );
321  $tpl->set( 'search', trim( $request->getVal( 'search' ) ) );
322  $tpl->set( 'stylepath', $wgStylePath );
323  $tpl->set( 'articlepath', $wgArticlePath );
324  $tpl->set( 'scriptpath', $wgScriptPath );
325  $tpl->set( 'serverurl', $wgServer );
326  $tpl->set( 'logopath', $wgLogo );
327  $tpl->set( 'sitename', $wgSitename );
328 
329  $userLang = $this->getLanguage();
330  $userLangCode = $userLang->getHtmlCode();
331  $userLangDir = $userLang->getDir();
332 
333  $tpl->set( 'lang', $userLangCode );
334  $tpl->set( 'dir', $userLangDir );
335  $tpl->set( 'rtl', $userLang->isRTL() );
336 
337  $tpl->set( 'capitalizeallnouns', $userLang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' );
338  $tpl->set( 'showjumplinks', true ); // showjumplinks preference has been removed
339  $tpl->set( 'username', $this->loggedin ? $this->username : null );
340  $tpl->set( 'userpage', $this->userpage );
341  $tpl->set( 'userpageurl', $this->userpageUrlDetails['href'] );
342  $tpl->set( 'userlang', $userLangCode );
343 
344  // Users can have their language set differently than the
345  // content of the wiki. For these users, tell the web browser
346  // that interface elements are in a different language.
347  $tpl->set( 'userlangattributes', '' );
348  $tpl->set( 'specialpageattributes', '' ); # obsolete
349  // Used by VectorBeta to insert HTML before content but after the
350  // heading for the page title. Defaults to empty string.
351  $tpl->set( 'prebodyhtml', '' );
352 
353  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
354  if (
355  $userLangCode !== $contLang->getHtmlCode() ||
356  $userLangDir !== $contLang->getDir()
357  ) {
358  $escUserlang = htmlspecialchars( $userLangCode );
359  $escUserdir = htmlspecialchars( $userLangDir );
360  // Attributes must be in double quotes because htmlspecialchars() doesn't
361  // escape single quotes
362  $attrs = " lang=\"$escUserlang\" dir=\"$escUserdir\"";
363  $tpl->set( 'userlangattributes', $attrs );
364  }
365 
366  $tpl->set( 'newtalk', $this->getNewtalks() );
367  $tpl->set( 'logo', $this->logoText() );
368 
369  $tpl->set( 'copyright', false );
370  // No longer used
371  $tpl->set( 'viewcount', false );
372  $tpl->set( 'lastmod', false );
373  $tpl->set( 'credits', false );
374  $tpl->set( 'numberofwatchingusers', false );
375  if ( $title->exists() ) {
376  if ( $out->isArticle() && $out->isRevisionCurrent() ) {
377  if ( $wgMaxCredits != 0 ) {
379  $action = Action::factory(
380  'credits', $this->getWikiPage(), $this->getContext() );
381  '@phan-var CreditsAction $action';
382  $tpl->set( 'credits',
383  $action->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) );
384  } else {
385  $tpl->set( 'lastmod', $this->lastModified() );
386  }
387  }
388  if ( $out->showsCopyright() ) {
389  $tpl->set( 'copyright', $this->getCopyright() );
390  }
391  }
392 
393  $tpl->set( 'copyrightico', $this->getCopyrightIcon() );
394  $tpl->set( 'poweredbyico', $this->getPoweredBy() );
395  $tpl->set( 'disclaimer', $this->disclaimerLink() );
396  $tpl->set( 'privacy', $this->privacyLink() );
397  $tpl->set( 'about', $this->aboutLink() );
398 
399  $tpl->set( 'footerlinks', [
400  'info' => [
401  'lastmod',
402  'numberofwatchingusers',
403  'credits',
404  'copyright',
405  ],
406  'places' => [
407  'privacy',
408  'about',
409  'disclaimer',
410  ],
411  ] );
412 
413  global $wgFooterIcons;
414  $tpl->set( 'footericons', $wgFooterIcons );
415  foreach ( $tpl->data['footericons'] as $footerIconsKey => &$footerIconsBlock ) {
416  if ( count( $footerIconsBlock ) > 0 ) {
417  foreach ( $footerIconsBlock as &$footerIcon ) {
418  if ( isset( $footerIcon['src'] ) ) {
419  if ( !isset( $footerIcon['width'] ) ) {
420  $footerIcon['width'] = 88;
421  }
422  if ( !isset( $footerIcon['height'] ) ) {
423  $footerIcon['height'] = 31;
424  }
425  }
426  }
427  } else {
428  unset( $tpl->data['footericons'][$footerIconsKey] );
429  }
430  }
431 
432  $tpl->set( 'indicators', $out->getIndicators() );
433 
434  $tpl->set( 'sitenotice', $this->getSiteNotice() );
435  $tpl->set( 'printfooter', $this->printSource() );
436  // Wrap the bodyText with #mw-content-text element
437  $out->mBodytext = $this->wrapHTML( $title, $out->mBodytext );
438  $tpl->set( 'bodytext', $out->mBodytext );
439 
440  $language_urls = $this->getLanguages();
441  if ( count( $language_urls ) ) {
442  $tpl->set( 'language_urls', $language_urls );
443  } else {
444  $tpl->set( 'language_urls', false );
445  }
446 
447  # Personal toolbar
448  $tpl->set( 'personal_urls', $this->buildPersonalUrls() );
449  $content_navigation = $this->buildContentNavigationUrls();
450  $content_actions = $this->buildContentActionUrls( $content_navigation );
451  $tpl->set( 'content_navigation', $content_navigation );
452  $tpl->set( 'content_actions', $content_actions );
453 
454  $tpl->set( 'sidebar', $this->buildSidebar() );
455  $tpl->set( 'nav_urls', $this->buildNavUrls() );
456 
457  // Do this last in case hooks above add bottom scripts
458  $tpl->set( 'bottomscripts', $this->bottomScripts() );
459 
460  // Set the head scripts near the end, in case the above actions resulted in added scripts
461  $tpl->set( 'headelement', $out->headElement( $this ) );
462 
463  $tpl->set( 'debug', '' );
464  $tpl->set( 'debughtml', $this->generateDebugHTML() );
465  $tpl->set( 'reporttime', wfReportTime( $out->getCSP()->getNonce() ) );
466 
467  // Avoid PHP 7.1 warning of passing $this by reference
468  $skinTemplate = $this;
469  // original version by hansm
470  if ( !Hooks::run( 'SkinTemplateOutputPageBeforeExec', [ &$skinTemplate, &$tpl ] ) ) {
471  wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" );
472  }
473 
474  // Set the bodytext to another key so that skins can just output it on its own
475  // and output printfooter and debughtml separately
476  $tpl->set( 'bodycontent', $tpl->data['bodytext'] );
477 
478  // Append printfooter and debughtml onto bodytext so that skins that
479  // were already using bodytext before they were split out don't suddenly
480  // start not outputting information.
481  $tpl->data['bodytext'] .= Html::rawElement(
482  'div',
483  [ 'class' => 'printfooter' ],
484  "\n{$tpl->data['printfooter']}"
485  ) . "\n";
486  $tpl->data['bodytext'] .= $tpl->data['debughtml'];
487 
488  // allow extensions adding stuff after the page content.
489  // See Skin::afterContentHook() for further documentation.
490  $tpl->set( 'dataAfterContent', $this->afterContentHook() );
491 
492  return $tpl;
493  }
494 
499  public function getPersonalToolsList() {
500  return $this->makePersonalToolsList();
501  }
502 
512  public function makePersonalToolsList( $personalTools = null, $options = [] ) {
513  $tpl = $this->setupTemplateForOutput();
514  $tpl->set( 'personal_urls', $this->buildPersonalUrls() );
515  $html = '';
516 
517  if ( $personalTools === null ) {
518  $personalTools = ( $tpl instanceof BaseTemplate )
519  ? $tpl->getPersonalTools()
520  : [];
521  }
522 
523  foreach ( $personalTools as $key => $item ) {
524  $html .= $tpl->makeListItem( $key, $item, $options );
525  }
526 
527  return $html;
528  }
529 
537  public function getStructuredPersonalTools() {
538  $tpl = $this->setupTemplateForOutput();
539  $tpl->set( 'personal_urls', $this->buildPersonalUrls() );
540 
541  return ( $tpl instanceof BaseTemplate ) ? $tpl->getPersonalTools() : [];
542  }
543 
552  function formatLanguageName( $name ) {
553  return $this->getLanguage()->ucfirst( $name );
554  }
555 
564  public function mapInterwikiToLanguage( $code ) {
565  $map = $this->getConfig()->get( 'InterlanguageLinkCodeMap' );
566  return $map[ $code ] ?? $code;
567  }
568 
577  function printOrError( $str ) {
578  echo $str;
579  }
580 
590  function useCombinedLoginLink() {
593  }
594 
599  protected function buildPersonalUrls() {
600  $title = $this->getTitle();
601  $request = $this->getRequest();
602  $pageurl = $title->getLocalURL();
603  $authManager = AuthManager::singleton();
604  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
605 
606  /* set up the default links for the personal toolbar */
607  $personal_urls = [];
608 
609  # Due to T34276, if a user does not have read permissions,
610  # $this->getTitle() will just give Special:Badtitle, which is
611  # not especially useful as a returnto parameter. Use the title
612  # from the request instead, if there was one.
613  if ( $permissionManager->userHasRight( $this->getUser(), 'read' ) ) {
614  $page = $this->getTitle();
615  } else {
616  $page = Title::newFromText( $request->getVal( 'title', '' ) );
617  }
618  $page = $request->getVal( 'returnto', $page );
619  $returnto = [];
620  if ( strval( $page ) !== '' ) {
621  $returnto['returnto'] = $page;
622  $query = $request->getVal( 'returntoquery', $this->thisquery );
623  $paramsArray = wfCgiToArray( $query );
624  $query = wfArrayToCgi( $paramsArray );
625  if ( $query != '' ) {
626  $returnto['returntoquery'] = $query;
627  }
628  }
629 
630  if ( $this->loggedin ) {
631  $personal_urls['userpage'] = [
632  'text' => $this->username,
633  'href' => &$this->userpageUrlDetails['href'],
634  'class' => $this->userpageUrlDetails['exists'] ? false : 'new',
635  'exists' => $this->userpageUrlDetails['exists'],
636  'active' => ( $this->userpageUrlDetails['href'] == $pageurl ),
637  'dir' => 'auto'
638  ];
639  $usertalkUrlDetails = $this->makeTalkUrlDetails( $this->userpage );
640  $personal_urls['mytalk'] = [
641  'text' => $this->msg( 'mytalk' )->text(),
642  'href' => &$usertalkUrlDetails['href'],
643  'class' => $usertalkUrlDetails['exists'] ? false : 'new',
644  'exists' => $usertalkUrlDetails['exists'],
645  'active' => ( $usertalkUrlDetails['href'] == $pageurl )
646  ];
647  $href = self::makeSpecialUrl( 'Preferences' );
648  $personal_urls['preferences'] = [
649  'text' => $this->msg( 'mypreferences' )->text(),
650  'href' => $href,
651  'active' => ( $href == $pageurl )
652  ];
653 
654  if ( $permissionManager->userHasRight( $this->getUser(), 'viewmywatchlist' ) ) {
655  $href = self::makeSpecialUrl( 'Watchlist' );
656  $personal_urls['watchlist'] = [
657  'text' => $this->msg( 'mywatchlist' )->text(),
658  'href' => $href,
659  'active' => ( $href == $pageurl )
660  ];
661  }
662 
663  # We need to do an explicit check for Special:Contributions, as we
664  # have to match both the title, and the target, which could come
665  # from request values (Special:Contributions?target=Jimbo_Wales)
666  # or be specified in "sub page" form
667  # (Special:Contributions/Jimbo_Wales). The plot
668  # thickens, because the Title object is altered for special pages,
669  # so it doesn't contain the original alias-with-subpage.
670  $origTitle = Title::newFromText( $request->getText( 'title' ) );
671  if ( $origTitle instanceof Title && $origTitle->isSpecialPage() ) {
672  list( $spName, $spPar ) =
673  MediaWikiServices::getInstance()->getSpecialPageFactory()->
674  resolveAlias( $origTitle->getText() );
675  $active = $spName == 'Contributions'
676  && ( ( $spPar && $spPar == $this->username )
677  || $request->getText( 'target' ) == $this->username );
678  } else {
679  $active = false;
680  }
681 
682  $href = self::makeSpecialUrlSubpage( 'Contributions', $this->username );
683  $personal_urls['mycontris'] = [
684  'text' => $this->msg( 'mycontris' )->text(),
685  'href' => $href,
686  'active' => $active
687  ];
688 
689  // if we can't set the user, we can't unset it either
690  if ( $request->getSession()->canSetUser() ) {
691  $personal_urls['logout'] = [
692  'text' => $this->msg( 'pt-userlogout' )->text(),
693  'href' => self::makeSpecialUrl( 'Userlogout',
694  // Note: userlogout link must always contain an & character, otherwise we might not be able
695  // to detect a buggy precaching proxy (T19790)
696  ( $title->isSpecial( 'Preferences' ) ? [] : $returnto ) ),
697  'active' => false
698  ];
699  }
700  } else {
701  $useCombinedLoginLink = $this->useCombinedLoginLink();
702  if ( !$authManager->canCreateAccounts() || !$authManager->canAuthenticateNow() ) {
703  // don't show combined login/signup link if one of those is actually not available
704  $useCombinedLoginLink = false;
705  }
706 
707  $loginlink = $permissionManager->userHasRight( $this->getUser(), 'createaccount' )
708  && $useCombinedLoginLink ? 'nav-login-createaccount' : 'pt-login';
709 
710  $login_url = [
711  'text' => $this->msg( $loginlink )->text(),
712  'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
713  'active' => $title->isSpecial( 'Userlogin' )
714  || $title->isSpecial( 'CreateAccount' ) && $useCombinedLoginLink,
715  ];
716  $createaccount_url = [
717  'text' => $this->msg( 'pt-createaccount' )->text(),
718  'href' => self::makeSpecialUrl( 'CreateAccount', $returnto ),
719  'active' => $title->isSpecial( 'CreateAccount' ),
720  ];
721 
722  // No need to show Talk and Contributions to anons if they can't contribute!
723  if ( $permissionManager->groupHasPermission( '*', 'edit' ) ) {
724  // Because of caching, we can't link directly to the IP talk and
725  // contributions pages. Instead we use the special page shortcuts
726  // (which work correctly regardless of caching). This means we can't
727  // determine whether these links are active or not, but since major
728  // skins (MonoBook, Vector) don't use this information, it's not a
729  // huge loss.
730  $personal_urls['anontalk'] = [
731  'text' => $this->msg( 'anontalk' )->text(),
732  'href' => self::makeSpecialUrlSubpage( 'Mytalk', false ),
733  'active' => false
734  ];
735  $personal_urls['anoncontribs'] = [
736  'text' => $this->msg( 'anoncontribs' )->text(),
737  'href' => self::makeSpecialUrlSubpage( 'Mycontributions', false ),
738  'active' => false
739  ];
740  }
741 
742  if (
743  $authManager->canCreateAccounts()
744  && $permissionManager->userHasRight( $this->getUser(), 'createaccount' )
745  && !$useCombinedLoginLink
746  ) {
747  $personal_urls['createaccount'] = $createaccount_url;
748  }
749 
750  if ( $authManager->canAuthenticateNow() ) {
751  $key = $permissionManager->groupHasPermission( '*', 'read' )
752  ? 'login'
753  : 'login-private';
754  $personal_urls[$key] = $login_url;
755  }
756  }
757 
758  Hooks::runWithoutAbort( 'PersonalUrls', [ &$personal_urls, &$title, $this ] );
759  return $personal_urls;
760  }
761 
773  function tabAction( $title, $message, $selected, $query = '', $checkEdit = false ) {
774  $classes = [];
775  if ( $selected ) {
776  $classes[] = 'selected';
777  }
778  $exists = true;
779  if ( $checkEdit && !$title->isKnown() ) {
780  $classes[] = 'new';
781  $exists = false;
782  if ( $query !== '' ) {
783  $query = 'action=edit&redlink=1&' . $query;
784  } else {
785  $query = 'action=edit&redlink=1';
786  }
787  }
788 
789  $services = MediaWikiServices::getInstance();
790  $linkClass = $services->getLinkRenderer()->getLinkClasses( $title );
791 
792  // wfMessageFallback will nicely accept $message as an array of fallbacks
793  // or just a single key
794  $msg = wfMessageFallback( $message )->setContext( $this->getContext() );
795  if ( is_array( $message ) ) {
796  // for hook compatibility just keep the last message name
797  $message = end( $message );
798  }
799  if ( $msg->exists() ) {
800  $text = $msg->text();
801  } else {
802  $text = $services->getContentLanguage()->getConverter()->
803  convertNamespace( $services->getNamespaceInfo()->
804  getSubject( $title->getNamespace() ) );
805  }
806 
807  // Avoid PHP 7.1 warning of passing $this by reference
808  $skinTemplate = $this;
809  $result = [];
810  if ( !Hooks::run( 'SkinTemplateTabAction', [ &$skinTemplate,
811  $title, $message, $selected, $checkEdit,
812  &$classes, &$query, &$text, &$result ] ) ) {
813  return $result;
814  }
815 
816  $result = [
817  'class' => implode( ' ', $classes ),
818  'text' => $text,
819  'href' => $title->getLocalURL( $query ),
820  'exists' => $exists,
821  'primary' => true ];
822  if ( $linkClass !== '' ) {
823  $result['link-class'] = $linkClass;
824  }
825 
826  return $result;
827  }
828 
829  function makeTalkUrlDetails( $name, $urlaction = '' ) {
830  $title = Title::newFromText( $name );
831  if ( !is_object( $title ) ) {
832  throw new MWException( __METHOD__ . " given invalid pagename $name" );
833  }
834  $title = $title->getTalkPage();
835  self::checkTitle( $title, $name );
836  return [
837  'href' => $title->getLocalURL( $urlaction ),
838  'exists' => $title->isKnown(),
839  ];
840  }
841 
848  function makeArticleUrlDetails( $name, $urlaction = '' ) {
849  $title = Title::newFromText( $name );
850  $title = $title->getSubjectPage();
851  self::checkTitle( $title, $name );
852  return [
853  'href' => $title->getLocalURL( $urlaction ),
854  'exists' => $title->exists(),
855  ];
856  }
857 
892  protected function buildContentNavigationUrls() {
894 
895  // Display tabs for the relevant title rather than always the title itself
896  $title = $this->getRelevantTitle();
897  $onPage = $title->equals( $this->getTitle() );
898 
899  $out = $this->getOutput();
900  $request = $this->getRequest();
901  $user = $this->getUser();
902  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
903 
904  $content_navigation = [
905  'namespaces' => [],
906  'views' => [],
907  'actions' => [],
908  'variants' => []
909  ];
910 
911  // parameters
912  $action = $request->getVal( 'action', 'view' );
913 
914  $userCanRead = $permissionManager->quickUserCan( 'read', $user, $title );
915 
916  // Avoid PHP 7.1 warning of passing $this by reference
917  $skinTemplate = $this;
918  $preventActiveTabs = false;
919  Hooks::run( 'SkinTemplatePreventOtherActiveTabs', [ &$skinTemplate, &$preventActiveTabs ] );
920 
921  // Checks if page is some kind of content
922  if ( $title->canExist() ) {
923  // Gets page objects for the related namespaces
924  $subjectPage = $title->getSubjectPage();
925  $talkPage = $title->getTalkPage();
926 
927  // Determines if this is a talk page
928  $isTalk = $title->isTalkPage();
929 
930  // Generates XML IDs from namespace names
931  $subjectId = $title->getNamespaceKey( '' );
932 
933  if ( $subjectId == 'main' ) {
934  $talkId = 'talk';
935  } else {
936  $talkId = "{$subjectId}_talk";
937  }
938 
939  $skname = $this->skinname;
940 
941  // Adds namespace links
942  $subjectMsg = [ "nstab-$subjectId" ];
943  if ( $subjectPage->isMainPage() ) {
944  array_unshift( $subjectMsg, 'mainpage-nstab' );
945  }
946  $content_navigation['namespaces'][$subjectId] = $this->tabAction(
947  $subjectPage, $subjectMsg, !$isTalk && !$preventActiveTabs, '', $userCanRead
948  );
949  $content_navigation['namespaces'][$subjectId]['context'] = 'subject';
950  $content_navigation['namespaces'][$talkId] = $this->tabAction(
951  $talkPage, [ "nstab-$talkId", 'talk' ], $isTalk && !$preventActiveTabs, '', $userCanRead
952  );
953  $content_navigation['namespaces'][$talkId]['context'] = 'talk';
954 
955  if ( $userCanRead ) {
956  // Adds "view" view link
957  if ( $title->isKnown() ) {
958  $content_navigation['views']['view'] = $this->tabAction(
959  $isTalk ? $talkPage : $subjectPage,
960  [ "$skname-view-view", 'view' ],
961  ( $onPage && ( $action == 'view' || $action == 'purge' ) ), '', true
962  );
963  // signal to hide this from simple content_actions
964  $content_navigation['views']['view']['redundant'] = true;
965  }
966 
967  $page = $this->canUseWikiPage() ? $this->getWikiPage() : false;
968  $isRemoteContent = $page && !$page->isLocal();
969 
970  // If it is a non-local file, show a link to the file in its own repository
971  // @todo abstract this for remote content that isn't a file
972  if ( $isRemoteContent ) {
973  $content_navigation['views']['view-foreign'] = [
974  'class' => '',
975  'text' => wfMessageFallback( "$skname-view-foreign", 'view-foreign' )->
976  setContext( $this->getContext() )->
977  params( $page->getWikiDisplayName() )->text(),
978  'href' => $page->getSourceURL(),
979  'primary' => false,
980  ];
981  }
982 
983  // Checks if user can edit the current page if it exists or create it otherwise
984  if ( $permissionManager->quickUserCan( 'edit', $user, $title ) &&
985  ( $title->exists() ||
986  $permissionManager->quickUserCan( 'create', $user, $title ) )
987  ) {
988  // Builds CSS class for talk page links
989  $isTalkClass = $isTalk ? ' istalk' : '';
990  // Whether the user is editing the page
991  $isEditing = $onPage && ( $action == 'edit' || $action == 'submit' );
992  // Whether to show the "Add a new section" tab
993  // Checks if this is a current rev of talk page and is not forced to be hidden
994  $showNewSection = !$out->forceHideNewSectionLink()
995  && ( ( $isTalk && $out->isRevisionCurrent() ) || $out->showNewSectionLink() );
996  $section = $request->getVal( 'section' );
997 
998  if ( $title->exists()
999  || ( $title->getNamespace() == NS_MEDIAWIKI
1000  && $title->getDefaultMessageText() !== false
1001  )
1002  ) {
1003  $msgKey = $isRemoteContent ? 'edit-local' : 'edit';
1004  } else {
1005  $msgKey = $isRemoteContent ? 'create-local' : 'create';
1006  }
1007  $content_navigation['views']['edit'] = [
1008  'class' => ( $isEditing && ( $section !== 'new' || !$showNewSection )
1009  ? 'selected'
1010  : ''
1011  ) . $isTalkClass,
1012  'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey )
1013  ->setContext( $this->getContext() )->text(),
1014  'href' => $title->getLocalURL( $this->editUrlOptions() ),
1015  'primary' => !$isRemoteContent, // don't collapse this in vector
1016  ];
1017 
1018  // section link
1019  if ( $showNewSection ) {
1020  // Adds new section link
1021  // $content_navigation['actions']['addsection']
1022  $content_navigation['views']['addsection'] = [
1023  'class' => ( $isEditing && $section == 'new' ) ? 'selected' : false,
1024  'text' => wfMessageFallback( "$skname-action-addsection", 'addsection' )
1025  ->setContext( $this->getContext() )->text(),
1026  'href' => $title->getLocalURL( 'action=edit&section=new' )
1027  ];
1028  }
1029  // Checks if the page has some kind of viewable source content
1030  } elseif ( $title->hasSourceText() ) {
1031  // Adds view source view link
1032  $content_navigation['views']['viewsource'] = [
1033  'class' => ( $onPage && $action == 'edit' ) ? 'selected' : false,
1034  'text' => wfMessageFallback( "$skname-action-viewsource", 'viewsource' )
1035  ->setContext( $this->getContext() )->text(),
1036  'href' => $title->getLocalURL( $this->editUrlOptions() ),
1037  'primary' => true, // don't collapse this in vector
1038  ];
1039  }
1040 
1041  // Checks if the page exists
1042  if ( $title->exists() ) {
1043  // Adds history view link
1044  $content_navigation['views']['history'] = [
1045  'class' => ( $onPage && $action == 'history' ) ? 'selected' : false,
1046  'text' => wfMessageFallback( "$skname-view-history", 'history_short' )
1047  ->setContext( $this->getContext() )->text(),
1048  'href' => $title->getLocalURL( 'action=history' ),
1049  ];
1050 
1051  if ( $permissionManager->quickUserCan( 'delete', $user, $title ) ) {
1052  $content_navigation['actions']['delete'] = [
1053  'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false,
1054  'text' => wfMessageFallback( "$skname-action-delete", 'delete' )
1055  ->setContext( $this->getContext() )->text(),
1056  'href' => $title->getLocalURL( 'action=delete' )
1057  ];
1058  }
1059 
1060  if ( $permissionManager->quickUserCan( 'move', $user, $title ) ) {
1061  $moveTitle = SpecialPage::getTitleFor( 'Movepage', $title->getPrefixedDBkey() );
1062  $content_navigation['actions']['move'] = [
1063  'class' => $this->getTitle()->isSpecial( 'Movepage' ) ? 'selected' : false,
1064  'text' => wfMessageFallback( "$skname-action-move", 'move' )
1065  ->setContext( $this->getContext() )->text(),
1066  'href' => $moveTitle->getLocalURL()
1067  ];
1068  }
1069  } else {
1070  // article doesn't exist or is deleted
1071  if ( $permissionManager->quickUserCan( 'deletedhistory', $user, $title ) ) {
1072  $n = $title->isDeleted();
1073  if ( $n ) {
1074  $undelTitle = SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() );
1075  // If the user can't undelete but can view deleted
1076  // history show them a "View .. deleted" tab instead.
1077  $msgKey = $permissionManager->quickUserCan( 'undelete',
1078  $user, $title ) ? 'undelete' : 'viewdeleted';
1079  $content_navigation['actions']['undelete'] = [
1080  'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
1081  'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
1082  ->setContext( $this->getContext() )->numParams( $n )->text(),
1083  'href' => $undelTitle->getLocalURL()
1084  ];
1085  }
1086  }
1087  }
1088 
1089  if ( $permissionManager->quickUserCan( 'protect', $user, $title ) &&
1090  $title->getRestrictionTypes() &&
1091  $permissionManager->getNamespaceRestrictionLevels( $title->getNamespace(), $user ) !== [ '' ]
1092  ) {
1093  $mode = $title->isProtected() ? 'unprotect' : 'protect';
1094  $content_navigation['actions'][$mode] = [
1095  'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
1096  'text' => wfMessageFallback( "$skname-action-$mode", $mode )
1097  ->setContext( $this->getContext() )->text(),
1098  'href' => $title->getLocalURL( "action=$mode" )
1099  ];
1100  }
1101 
1102  // Checks if the user is logged in
1103  if ( $this->loggedin && $permissionManager->userHasAllRights( $user,
1104  'viewmywatchlist', 'editmywatchlist' )
1105  ) {
1115  $mode = $user->isWatched( $title ) ? 'unwatch' : 'watch';
1116  $content_navigation['actions'][$mode] = [
1117  'class' => 'mw-watchlink ' . (
1118  $onPage && ( $action == 'watch' || $action == 'unwatch' ) ? 'selected' : ''
1119  ),
1120  // uses 'watch' or 'unwatch' message
1121  'text' => $this->msg( $mode )->text(),
1122  'href' => $title->getLocalURL( [ 'action' => $mode ] ),
1123  // Set a data-mw=interface attribute, which the mediawiki.page.ajax
1124  // module will look for to make sure it's a trusted link
1125  'data' => [
1126  'mw' => 'interface',
1127  ],
1128  ];
1129  }
1130  }
1131 
1132  // Avoid PHP 7.1 warning of passing $this by reference
1133  $skinTemplate = $this;
1135  'SkinTemplateNavigation',
1136  [ &$skinTemplate, &$content_navigation ]
1137  );
1138 
1139  if ( $userCanRead && !$wgDisableLangConversion ) {
1140  $pageLang = $title->getPageLanguage();
1141  // Checks that language conversion is enabled and variants exist
1142  // And if it is not in the special namespace
1143  if ( $pageLang->hasVariants() ) {
1144  // Gets list of language variants
1145  $variants = $pageLang->getVariants();
1146  // Gets preferred variant (note that user preference is
1147  // only possible for wiki content language variant)
1148  $preferred = $pageLang->getPreferredVariant();
1149  if ( Action::getActionName( $this ) === 'view' ) {
1150  $params = $request->getQueryValues();
1151  unset( $params['title'] );
1152  } else {
1153  $params = [];
1154  }
1155  // Loops over each variant
1156  foreach ( $variants as $code ) {
1157  // Gets variant name from language code
1158  $varname = $pageLang->getVariantname( $code );
1159  // Appends variant link
1160  $content_navigation['variants'][] = [
1161  'class' => ( $code == $preferred ) ? 'selected' : false,
1162  'text' => $varname,
1163  'href' => $title->getLocalURL( [ 'variant' => $code ] + $params ),
1164  'lang' => LanguageCode::bcp47( $code ),
1165  'hreflang' => LanguageCode::bcp47( $code ),
1166  ];
1167  }
1168  }
1169  }
1170  } else {
1171  // If it's not content, it's got to be a special page
1172  $content_navigation['namespaces']['special'] = [
1173  'class' => 'selected',
1174  'text' => $this->msg( 'nstab-special' )->text(),
1175  'href' => $request->getRequestURL(), // @see: T4457, T4510
1176  'context' => 'subject'
1177  ];
1178 
1179  // Avoid PHP 7.1 warning of passing $this by reference
1180  $skinTemplate = $this;
1181  Hooks::runWithoutAbort( 'SkinTemplateNavigation::SpecialPage',
1182  [ &$skinTemplate, &$content_navigation ] );
1183  }
1184 
1185  // Avoid PHP 7.1 warning of passing $this by reference
1186  $skinTemplate = $this;
1187  // Equiv to SkinTemplateContentActions
1188  Hooks::runWithoutAbort( 'SkinTemplateNavigation::Universal',
1189  [ &$skinTemplate, &$content_navigation ] );
1190 
1191  // Setup xml ids and tooltip info
1192  foreach ( $content_navigation as $section => &$links ) {
1193  foreach ( $links as $key => &$link ) {
1194  $xmlID = $key;
1195  if ( isset( $link['context'] ) && $link['context'] == 'subject' ) {
1196  $xmlID = 'ca-nstab-' . $xmlID;
1197  } elseif ( isset( $link['context'] ) && $link['context'] == 'talk' ) {
1198  $xmlID = 'ca-talk';
1199  $link['rel'] = 'discussion';
1200  } elseif ( $section == 'variants' ) {
1201  $xmlID = 'ca-varlang-' . $xmlID;
1202  } else {
1203  $xmlID = 'ca-' . $xmlID;
1204  }
1205  $link['id'] = $xmlID;
1206  }
1207  }
1208 
1209  # We don't want to give the watch tab an accesskey if the
1210  # page is being edited, because that conflicts with the
1211  # accesskey on the watch checkbox. We also don't want to
1212  # give the edit tab an accesskey, because that's fairly
1213  # superfluous and conflicts with an accesskey (Ctrl-E) often
1214  # used for editing in Safari.
1215  if ( in_array( $action, [ 'edit', 'submit' ] ) ) {
1216  if ( isset( $content_navigation['views']['edit'] ) ) {
1217  $content_navigation['views']['edit']['tooltiponly'] = true;
1218  }
1219  if ( isset( $content_navigation['actions']['watch'] ) ) {
1220  $content_navigation['actions']['watch']['tooltiponly'] = true;
1221  }
1222  if ( isset( $content_navigation['actions']['unwatch'] ) ) {
1223  $content_navigation['actions']['unwatch']['tooltiponly'] = true;
1224  }
1225  }
1226 
1227  return $content_navigation;
1228  }
1229 
1235  private function buildContentActionUrls( $content_navigation ) {
1236  // content_actions has been replaced with content_navigation for backwards
1237  // compatibility and also for skins that just want simple tabs content_actions
1238  // is now built by flattening the content_navigation arrays into one
1239 
1240  $content_actions = [];
1241 
1242  foreach ( $content_navigation as $links ) {
1243  foreach ( $links as $key => $value ) {
1244  if ( isset( $value['redundant'] ) && $value['redundant'] ) {
1245  // Redundant tabs are dropped from content_actions
1246  continue;
1247  }
1248 
1249  // content_actions used to have ids built using the "ca-$key" pattern
1250  // so the xmlID based id is much closer to the actual $key that we want
1251  // for that reason we'll just strip out the ca- if present and use
1252  // the latter potion of the "id" as the $key
1253  if ( isset( $value['id'] ) && substr( $value['id'], 0, 3 ) == 'ca-' ) {
1254  $key = substr( $value['id'], 3 );
1255  }
1256 
1257  if ( isset( $content_actions[$key] ) ) {
1258  wfDebug( __METHOD__ . ": Found a duplicate key for $key while flattening " .
1259  "content_navigation into content_actions.\n" );
1260  continue;
1261  }
1262 
1263  $content_actions[$key] = $value;
1264  }
1265  }
1266 
1267  return $content_actions;
1268  }
1269 
1274  protected function buildNavUrls() {
1275  global $wgUploadNavigationUrl;
1276 
1277  $out = $this->getOutput();
1278  $request = $this->getRequest();
1279 
1280  $nav_urls = [];
1281  $nav_urls['mainpage'] = [ 'href' => self::makeMainPageUrl() ];
1282  if ( $wgUploadNavigationUrl ) {
1283  $nav_urls['upload'] = [ 'href' => $wgUploadNavigationUrl ];
1284  } elseif ( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) {
1285  $nav_urls['upload'] = [ 'href' => self::makeSpecialUrl( 'Upload' ) ];
1286  } else {
1287  $nav_urls['upload'] = false;
1288  }
1289  $nav_urls['specialpages'] = [ 'href' => self::makeSpecialUrl( 'Specialpages' ) ];
1290 
1291  $nav_urls['print'] = false;
1292  $nav_urls['permalink'] = false;
1293  $nav_urls['info'] = false;
1294  $nav_urls['whatlinkshere'] = false;
1295  $nav_urls['recentchangeslinked'] = false;
1296  $nav_urls['contributions'] = false;
1297  $nav_urls['log'] = false;
1298  $nav_urls['blockip'] = false;
1299  $nav_urls['mute'] = false;
1300  $nav_urls['emailuser'] = false;
1301  $nav_urls['userrights'] = false;
1302 
1303  // A print stylesheet is attached to all pages, but nobody ever
1304  // figures that out. :) Add a link...
1305  if ( !$out->isPrintable() && ( $out->isArticle() || $this->getTitle()->isSpecialPage() ) ) {
1306  $nav_urls['print'] = [
1307  'text' => $this->msg( 'printableversion' )->text(),
1308  'href' => $this->getTitle()->getLocalURL(
1309  $request->appendQueryValue( 'printable', 'yes' ) )
1310  ];
1311  }
1312 
1313  if ( $out->isArticle() ) {
1314  // Also add a "permalink" while we're at it
1315  $revid = $this->getOutput()->getRevisionId();
1316  if ( $revid ) {
1317  $nav_urls['permalink'] = [
1318  'text' => $this->msg( 'permalink' )->text(),
1319  'href' => $this->getTitle()->getLocalURL( "oldid=$revid" )
1320  ];
1321  }
1322 
1323  // Avoid PHP 7.1 warning of passing $this by reference
1324  $skinTemplate = $this;
1325  // Use the copy of revision ID in case this undocumented, shady hook tries to mess with internals
1326  Hooks::run( 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink',
1327  [ &$skinTemplate, &$nav_urls, &$revid, &$revid ] );
1328  }
1329 
1330  if ( $out->isArticleRelated() ) {
1331  $nav_urls['whatlinkshere'] = [
1332  'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage )->getLocalURL()
1333  ];
1334 
1335  $nav_urls['info'] = [
1336  'text' => $this->msg( 'pageinfo-toolboxlink' )->text(),
1337  'href' => $this->getTitle()->getLocalURL( "action=info" )
1338  ];
1339 
1340  if ( $this->getTitle()->exists() || $this->getTitle()->inNamespace( NS_CATEGORY ) ) {
1341  $nav_urls['recentchangeslinked'] = [
1342  'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage )->getLocalURL()
1343  ];
1344  }
1345  }
1346 
1347  $user = $this->getRelevantUser();
1348  if ( $user ) {
1349  $rootUser = $user->getName();
1350 
1351  $nav_urls['contributions'] = [
1352  'text' => $this->msg( 'contributions', $rootUser )->text(),
1353  'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser ),
1354  'tooltip-params' => [ $rootUser ],
1355  ];
1356 
1357  $nav_urls['log'] = [
1358  'href' => self::makeSpecialUrlSubpage( 'Log', $rootUser )
1359  ];
1360 
1361  if ( MediaWikiServices::getInstance()
1363  ->userHasRight( $this->getUser(), 'block' )
1364  ) {
1365  $nav_urls['blockip'] = [
1366  'text' => $this->msg( 'blockip', $rootUser )->text(),
1367  'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser )
1368  ];
1369  }
1370 
1371  if ( $this->showEmailUser( $user ) ) {
1372  $nav_urls['emailuser'] = [
1373  'text' => $this->msg( 'tool-link-emailuser', $rootUser )->text(),
1374  'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser ),
1375  'tooltip-params' => [ $rootUser ],
1376  ];
1377  }
1378 
1379  if ( !$user->isAnon() ) {
1380  if ( $this->getUser()->isRegistered() && $this->getConfig()->get( 'EnableSpecialMute' ) ) {
1381  $nav_urls['mute'] = [
1382  'text' => $this->msg( 'mute-preferences' )->text(),
1383  'href' => self::makeSpecialUrlSubpage( 'Mute', $rootUser )
1384  ];
1385  }
1386 
1387  $sur = new UserrightsPage;
1388  $sur->setContext( $this->getContext() );
1389  $canChange = $sur->userCanChangeRights( $user );
1390  $nav_urls['userrights'] = [
1391  'text' => $this->msg(
1392  $canChange ? 'tool-link-userrights' : 'tool-link-userrights-readonly',
1393  $rootUser
1394  )->text(),
1395  'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
1396  ];
1397  }
1398  }
1399 
1400  return $nav_urls;
1401  }
1402 
1407  protected function getNameSpaceKey() {
1408  return $this->getTitle()->getNamespaceKey();
1409  }
1410 }
setContext(IContextSource $context)
makePersonalToolsList( $personalTools=null, $options=[])
Get the HTML for the personal tools list.
$wgShowCreditsIfMax
If there are more than $wgMaxCredits authors, show $wgMaxCredits of them.
showEmailUser( $id)
Definition: Skin.php:1116
makeArticleUrlDetails( $name, $urlaction='')
$wgFooterIcons
Abstract list of footer icons for skins in place of old copyrightico and poweredbyico code You can ad...
lastModified()
Get the timestamp of the latest revision, formatted in user language.
Definition: Skin.php:945
outputPage()
Initialize various variables and generate the template.
$wgScript
The URL path to index.php.
afterContentHook()
This runs a hook to allow extensions placing their stuff after content and article metadata (e...
Definition: Skin.php:649
getPoweredBy()
Gets the powered by MediaWiki icon.
Definition: Skin.php:922
getNewtalks()
Gets new talk page messages for the current user and returns an appropriate alert message (or an empt...
Definition: Skin.php:1469
getLanguages()
Generates array of language links for the current page.
$wgSitename
Name of the site.
wrapHTML( $title, $html)
Wrap the body text with language information and identifiable element.
buildSidebar()
Build an array that represents the sidebar(s), the navigation bar among them.
Definition: Skin.php:1316
$wgMimeType
The default Content-Type header.
static instance()
Singleton.
Definition: Profiler.php:63
privacyLink()
Gets the link to the wiki&#39;s privacy policy page.
Definition: Skin.php:1075
string $template
For QuickTemplate, the name of the subclass which will actually fill the template.
static factory( $action, Page $page, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:97
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
initPage(OutputPage $out)
Definition: Skin.php:165
editUrlOptions()
Return URL options for the &#39;edit page&#39; link.
Definition: Skin.php:1102
buildContentNavigationUrls()
a structured array of links usually used for the tabs in a skin
New base template for a skin&#39;s template extended from QuickTemplate this class features helper method...
const NS_SPECIAL
Definition: Defines.php:49
getCopyrightIcon()
Definition: Skin.php:892
$wgArticlePath
Definition: img_auth.php:47
getCategories()
Definition: Skin.php:614
getSiteNotice()
Get the site notice.
Definition: Skin.php:1612
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
getPermissionManager()
$wgStylePath
The URL path of the skins directory.
wfReportTime( $nonce=null)
Returns a script tag that stores the amount of time it took MediaWiki to handle the request in millis...
setContext( $context)
Sets the context this SpecialPage is executed in.
getStructuredPersonalTools()
Get personal tools for the user.
setupTemplate( $classname)
Create the template engine object; we feed it a bunch of data and eventually it spits out some HTML...
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
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:149
useCombinedLoginLink()
Output a boolean indicating if buildPersonalUrls should output separate login and create account link...
Base class for template-based skins.
getRelevantTitle()
Return the "relevant" title.
Definition: Skin.php:346
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
subPageSubtitle( $out=null)
Definition: Skin.php:772
disclaimerLink()
Gets the link to the wiki&#39;s general disclaimers page.
Definition: Skin.php:1091
wfMessageFallback(... $keys)
This function accepts multiple message keys and returns a message instance for the first message whic...
getContext()
Get the base IContextSource object.
getSearchLink()
Definition: Skin.php:831
const NS_CATEGORY
Definition: Defines.php:74
generateDebugHTML()
Generate debug data HTML for displaying at the bottom of the main content area.
Definition: Skin.php:675
static runWithoutAbort( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:231
mapInterwikiToLanguage( $code)
Allows correcting the language of interlanguage links which, mostly due to legacy reasons...
getRelevantUser()
Return the "relevant" user.
Definition: Skin.php:367
static getActionName(IContextSource $context)
Get the action that will be executed, not necessarily the one passed passed through the "action" requ...
Definition: Action.php:123
aboutLink()
Gets the link to the wiki&#39;s about page.
Definition: Skin.php:1083
const NS_FILE
Definition: Defines.php:66
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:83
logoText( $align='')
Definition: Skin.php:973
$wgDisableLangConversion
Whether to enable language variant conversion.
const NS_MEDIAWIKI
Definition: Defines.php:68
static isEnabled()
Returns true if uploads are enabled.
Definition: UploadBase.php:135
static fetchLanguageName( $code, $inLanguage=self::AS_AUTONYMS, $include=self::ALL)
Definition: Language.php:868
buildContentActionUrls( $content_navigation)
an array of edit links by default used for the tabs
printSource()
Text with the permalink to the source page, usually shown on the footer of a printed page...
Definition: Skin.php:706
tabAction( $title, $message, $selected, $query='', $checkEdit=false)
Builds an array with tab definition.
$wgHideInterlanguageLinks
Hide interlanguage links from the sidebar.
buildPersonalUrls()
build array of urls for personal toolbar
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
$wgScriptPath
The path we should point to.
string $skinname
Name of our skin, it probably needs to be all lower case.
getNameSpaceKey()
Generate strings used for xml &#39;id&#39; names.
$wgMaxCredits
Set this to the number of authors that you want to be credited below an article text.
getCopyright( $type='detect')
Definition: Skin.php:849
prepareQuickTemplate()
initialize various variables and generate the template
$wgLogo
The URL path of the wiki logo.
Special page to allow managing user group membership.
formatLanguageName( $name)
Format language name for use in sidebar interlanguage links list.
getUndeleteLink()
Definition: Skin.php:724
$wgServer
URL of the server.
makeTalkUrlDetails( $name, $urlaction='')
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$wgUseCombinedLoginLink
Login / create account link behavior when it&#39;s possible for anonymous users to create an account...
$wgUploadNavigationUrl
Point the upload navigation link to an external URL Useful if you want to use a shared repository by ...
getWikiPage()
Get the WikiPage object.
bottomScripts()
This gets called shortly before the "</body>" tag.
Definition: Skin.php:684
return true
Definition: router.php:92
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
printOrError( $str)
Output the string, or print error message if it&#39;s an error object of the appropriate type...
buildNavUrls()
build array of common navigation links
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:317
getPersonalToolsList()
Get the HTML for the p-personal list.