MediaWiki  REL1_24
SkinTemplate.php
Go to the documentation of this file.
00001 <?php
00030 class MediaWikiI18N {
00031     private $context = array();
00032 
00033     function set( $varName, $value ) {
00034         $this->context[$varName] = $value;
00035     }
00036 
00037     function translate( $value ) {
00038         wfProfileIn( __METHOD__ );
00039 
00040         // Hack for i18n:attributes in PHPTAL 1.0.0 dev version as of 2004-10-23
00041         $value = preg_replace( '/^string:/', '', $value );
00042 
00043         $value = wfMessage( $value )->text();
00044         // interpolate variables
00045         $m = array();
00046         while ( preg_match( '/\$([0-9]*?)/sm', $value, $m ) ) {
00047             list( $src, $var ) = $m;
00048             wfSuppressWarnings();
00049             $varValue = $this->context[$var];
00050             wfRestoreWarnings();
00051             $value = str_replace( $src, $varValue, $value );
00052         }
00053         wfProfileOut( __METHOD__ );
00054         return $value;
00055     }
00056 }
00057 
00070 class SkinTemplate extends Skin {
00075     public $skinname = 'monobook';
00076 
00081     public $template = 'QuickTemplate';
00082 
00088     function setupSkinUserCss( OutputPage $out ) {
00089         $out->addModuleStyles( array(
00090             'mediawiki.legacy.shared',
00091             'mediawiki.legacy.commonPrint',
00092             'mediawiki.ui.button'
00093         ) );
00094     }
00095 
00107     function setupTemplate( $classname, $repository = false, $cache_dir = false ) {
00108         return new $classname( $this->getConfig() );
00109     }
00110 
00116     public function getLanguages() {
00117         global $wgHideInterlanguageLinks;
00118         if ( $wgHideInterlanguageLinks ) {
00119             return array();
00120         }
00121 
00122         $userLang = $this->getLanguage();
00123         $languageLinks = array();
00124 
00125         foreach ( $this->getOutput()->getLanguageLinks() as $languageLinkText ) {
00126             $languageLinkParts = explode( ':', $languageLinkText, 2 );
00127             $class = 'interlanguage-link interwiki-' . $languageLinkParts[0];
00128             unset( $languageLinkParts );
00129 
00130             $languageLinkTitle = Title::newFromText( $languageLinkText );
00131             if ( $languageLinkTitle ) {
00132                 $ilInterwikiCode = $languageLinkTitle->getInterwiki();
00133                 $ilLangName = Language::fetchLanguageName( $ilInterwikiCode );
00134 
00135                 if ( strval( $ilLangName ) === '' ) {
00136                     $ilDisplayTextMsg = wfMessage( "interlanguage-link-$ilInterwikiCode" );
00137                     if ( !$ilDisplayTextMsg->isDisabled() ) {
00138                         // Use custom MW message for the display text
00139                         $ilLangName = $ilDisplayTextMsg->text();
00140                     } else {
00141                         // Last resort: fallback to the language link target
00142                         $ilLangName = $languageLinkText;
00143                     }
00144                 } else {
00145                     // Use the language autonym as display text
00146                     $ilLangName = $this->formatLanguageName( $ilLangName );
00147                 }
00148 
00149                 // CLDR extension or similar is required to localize the language name;
00150                 // otherwise we'll end up with the autonym again.
00151                 $ilLangLocalName = Language::fetchLanguageName(
00152                     $ilInterwikiCode,
00153                     $userLang->getCode()
00154                 );
00155 
00156                 $languageLinkTitleText = $languageLinkTitle->getText();
00157                 if ( $ilLangLocalName === '' ) {
00158                     $ilFriendlySiteName = wfMessage( "interlanguage-link-sitename-$ilInterwikiCode" );
00159                     if ( !$ilFriendlySiteName->isDisabled() ) {
00160                         if ( $languageLinkTitleText === '' ) {
00161                             $ilTitle = wfMessage(
00162                                 'interlanguage-link-title-nonlangonly',
00163                                 $ilFriendlySiteName->text()
00164                             )->text();
00165                         } else {
00166                             $ilTitle = wfMessage(
00167                                 'interlanguage-link-title-nonlang',
00168                                 $languageLinkTitleText,
00169                                 $ilFriendlySiteName->text()
00170                             )->text();
00171                         }
00172                     } else {
00173                         // we have nothing friendly to put in the title, so fall back to
00174                         // displaying the interlanguage link itself in the title text
00175                         // (similar to what is done in page content)
00176                         $ilTitle = $languageLinkTitle->getInterwiki() .
00177                             ":$languageLinkTitleText";
00178                     }
00179                 } elseif ( $languageLinkTitleText === '' ) {
00180                     $ilTitle = wfMessage(
00181                         'interlanguage-link-title-langonly',
00182                         $ilLangLocalName
00183                     )->text();
00184                 } else {
00185                     $ilTitle = wfMessage(
00186                         'interlanguage-link-title',
00187                         $languageLinkTitleText,
00188                         $ilLangLocalName
00189                     )->text();
00190                 }
00191 
00192                 $ilInterwikiCodeBCP47 = wfBCP47( $ilInterwikiCode );
00193                 $languageLink = array(
00194                     'href' => $languageLinkTitle->getFullURL(),
00195                     'text' => $ilLangName,
00196                     'title' => $ilTitle,
00197                     'class' => $class,
00198                     'lang' => $ilInterwikiCodeBCP47,
00199                     'hreflang' => $ilInterwikiCodeBCP47,
00200                 );
00201                 wfRunHooks(
00202                     'SkinTemplateGetLanguageLink',
00203                     array( &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() )
00204                 );
00205                 $languageLinks[] = $languageLink;
00206             }
00207         }
00208 
00209         return $languageLinks;
00210     }
00211 
00212     protected function setupTemplateForOutput() {
00213         wfProfileIn( __METHOD__ );
00214 
00215         $request = $this->getRequest();
00216         $user = $this->getUser();
00217         $title = $this->getTitle();
00218 
00219         wfProfileIn( __METHOD__ . '-init' );
00220         $tpl = $this->setupTemplate( $this->template, 'skins' );
00221         wfProfileOut( __METHOD__ . '-init' );
00222 
00223         wfProfileIn( __METHOD__ . '-stuff' );
00224         $this->thispage = $title->getPrefixedDBkey();
00225         $this->titletxt = $title->getPrefixedText();
00226         $this->userpage = $user->getUserPage()->getPrefixedText();
00227         $query = array();
00228         if ( !$request->wasPosted() ) {
00229             $query = $request->getValues();
00230             unset( $query['title'] );
00231             unset( $query['returnto'] );
00232             unset( $query['returntoquery'] );
00233         }
00234         $this->thisquery = wfArrayToCgi( $query );
00235         $this->loggedin = $user->isLoggedIn();
00236         $this->username = $user->getName();
00237 
00238         if ( $this->loggedin || $this->showIPinHeader() ) {
00239             $this->userpageUrlDetails = self::makeUrlDetails( $this->userpage );
00240         } else {
00241             # This won't be used in the standard skins, but we define it to preserve the interface
00242             # To save time, we check for existence
00243             $this->userpageUrlDetails = self::makeKnownUrlDetails( $this->userpage );
00244         }
00245 
00246         wfProfileOut( __METHOD__ . '-stuff' );
00247 
00248         wfProfileOut( __METHOD__ );
00249 
00250         return $tpl;
00251     }
00252 
00258     function outputPage( OutputPage $out = null ) {
00259         wfProfileIn( __METHOD__ );
00260         Profiler::instance()->setTemplated( true );
00261 
00262         $oldContext = null;
00263         if ( $out !== null ) {
00264             // @todo Add wfDeprecated in 1.20
00265             $oldContext = $this->getContext();
00266             $this->setContext( $out->getContext() );
00267         }
00268 
00269         $out = $this->getOutput();
00270 
00271         wfProfileIn( __METHOD__ . '-init' );
00272         $this->initPage( $out );
00273         wfProfileOut( __METHOD__ . '-init' );
00274         $tpl = $this->prepareQuickTemplate( $out );
00275         // execute template
00276         wfProfileIn( __METHOD__ . '-execute' );
00277         $res = $tpl->execute();
00278         wfProfileOut( __METHOD__ . '-execute' );
00279 
00280         // result may be an error
00281         $this->printOrError( $res );
00282 
00283         if ( $oldContext ) {
00284             $this->setContext( $oldContext );
00285         }
00286 
00287         wfProfileOut( __METHOD__ );
00288     }
00289 
00296     protected function prepareQuickTemplate() {
00297         global $wgContLang, $wgScript, $wgStylePath, $wgMimeType, $wgJsMimeType,
00298             $wgDisableCounters, $wgSitename, $wgLogo, $wgMaxCredits,
00299             $wgShowCreditsIfMax, $wgPageShowWatchingUsers, $wgArticlePath,
00300             $wgScriptPath, $wgServer;
00301 
00302         wfProfileIn( __METHOD__ );
00303 
00304         $title = $this->getTitle();
00305         $request = $this->getRequest();
00306         $out = $this->getOutput();
00307         $tpl = $this->setupTemplateForOutput();
00308 
00309         wfProfileIn( __METHOD__ . '-stuff2' );
00310         $tpl->set( 'title', $out->getPageTitle() );
00311         $tpl->set( 'pagetitle', $out->getHTMLTitle() );
00312         $tpl->set( 'displaytitle', $out->mPageLinkTitle );
00313 
00314         $tpl->setRef( 'thispage', $this->thispage );
00315         $tpl->setRef( 'titleprefixeddbkey', $this->thispage );
00316         $tpl->set( 'titletext', $title->getText() );
00317         $tpl->set( 'articleid', $title->getArticleID() );
00318 
00319         $tpl->set( 'isarticle', $out->isArticle() );
00320 
00321         $subpagestr = $this->subPageSubtitle();
00322         if ( $subpagestr !== '' ) {
00323             $subpagestr = '<span class="subpages">' . $subpagestr . '</span>';
00324         }
00325         $tpl->set( 'subtitle', $subpagestr . $out->getSubtitle() );
00326 
00327         $undelete = $this->getUndeleteLink();
00328         if ( $undelete === '' ) {
00329             $tpl->set( 'undelete', '' );
00330         } else {
00331             $tpl->set( 'undelete', '<span class="subpages">' . $undelete . '</span>' );
00332         }
00333 
00334         $tpl->set( 'catlinks', $this->getCategories() );
00335         if ( $out->isSyndicated() ) {
00336             $feeds = array();
00337             foreach ( $out->getSyndicationLinks() as $format => $link ) {
00338                 $feeds[$format] = array(
00339                     // Messages: feed-atom, feed-rss
00340                     'text' => $this->msg( "feed-$format" )->text(),
00341                     'href' => $link
00342                 );
00343             }
00344             $tpl->setRef( 'feeds', $feeds );
00345         } else {
00346             $tpl->set( 'feeds', false );
00347         }
00348 
00349         $tpl->setRef( 'mimetype', $wgMimeType );
00350         $tpl->setRef( 'jsmimetype', $wgJsMimeType );
00351         $tpl->set( 'charset', 'UTF-8' );
00352         $tpl->setRef( 'wgScript', $wgScript );
00353         $tpl->setRef( 'skinname', $this->skinname );
00354         $tpl->set( 'skinclass', get_class( $this ) );
00355         $tpl->setRef( 'skin', $this );
00356         $tpl->setRef( 'stylename', $this->stylename );
00357         $tpl->set( 'printable', $out->isPrintable() );
00358         $tpl->set( 'handheld', $request->getBool( 'handheld' ) );
00359         $tpl->setRef( 'loggedin', $this->loggedin );
00360         $tpl->set( 'notspecialpage', !$title->isSpecialPage() );
00361         $tpl->set( 'searchaction', $this->escapeSearchLink() );
00362         $tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBkey() );
00363         $tpl->set( 'search', trim( $request->getVal( 'search' ) ) );
00364         $tpl->setRef( 'stylepath', $wgStylePath );
00365         $tpl->setRef( 'articlepath', $wgArticlePath );
00366         $tpl->setRef( 'scriptpath', $wgScriptPath );
00367         $tpl->setRef( 'serverurl', $wgServer );
00368         $tpl->setRef( 'logopath', $wgLogo );
00369         $tpl->setRef( 'sitename', $wgSitename );
00370 
00371         $userLang = $this->getLanguage();
00372         $userLangCode = $userLang->getHtmlCode();
00373         $userLangDir = $userLang->getDir();
00374 
00375         $tpl->set( 'lang', $userLangCode );
00376         $tpl->set( 'dir', $userLangDir );
00377         $tpl->set( 'rtl', $userLang->isRTL() );
00378 
00379         $tpl->set( 'capitalizeallnouns', $userLang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' );
00380         $tpl->set( 'showjumplinks', true ); // showjumplinks preference has been removed
00381         $tpl->set( 'username', $this->loggedin ? $this->username : null );
00382         $tpl->setRef( 'userpage', $this->userpage );
00383         $tpl->setRef( 'userpageurl', $this->userpageUrlDetails['href'] );
00384         $tpl->set( 'userlang', $userLangCode );
00385 
00386         // Users can have their language set differently than the
00387         // content of the wiki. For these users, tell the web browser
00388         // that interface elements are in a different language.
00389         $tpl->set( 'userlangattributes', '' );
00390         $tpl->set( 'specialpageattributes', '' ); # obsolete
00391         // Used by VectorBeta to insert HTML before content but after the
00392         // heading for the page title. Defaults to empty string.
00393         $tpl->set( 'prebodyhtml', '' );
00394 
00395         if ( $userLangCode !== $wgContLang->getHtmlCode() || $userLangDir !== $wgContLang->getDir() ) {
00396             $escUserlang = htmlspecialchars( $userLangCode );
00397             $escUserdir = htmlspecialchars( $userLangDir );
00398             // Attributes must be in double quotes because htmlspecialchars() doesn't
00399             // escape single quotes
00400             $attrs = " lang=\"$escUserlang\" dir=\"$escUserdir\"";
00401             $tpl->set( 'userlangattributes', $attrs );
00402         }
00403 
00404         wfProfileOut( __METHOD__ . '-stuff2' );
00405 
00406         wfProfileIn( __METHOD__ . '-stuff3' );
00407         $tpl->set( 'newtalk', $this->getNewtalks() );
00408         $tpl->set( 'logo', $this->logoText() );
00409 
00410         $tpl->set( 'copyright', false );
00411         $tpl->set( 'viewcount', false );
00412         $tpl->set( 'lastmod', false );
00413         $tpl->set( 'credits', false );
00414         $tpl->set( 'numberofwatchingusers', false );
00415         if ( $out->isArticle() && $title->exists() ) {
00416             if ( $this->isRevisionCurrent() ) {
00417                 if ( !$wgDisableCounters ) {
00418                     $viewcount = $this->getWikiPage()->getCount();
00419                     if ( $viewcount ) {
00420                         $tpl->set( 'viewcount', $this->msg( 'viewcount' )->numParams( $viewcount )->parse() );
00421                     }
00422                 }
00423 
00424                 if ( $wgPageShowWatchingUsers ) {
00425                     $dbr = wfGetDB( DB_SLAVE );
00426                     $num = $dbr->selectField( 'watchlist', 'COUNT(*)',
00427                         array( 'wl_title' => $title->getDBkey(), 'wl_namespace' => $title->getNamespace() ),
00428                         __METHOD__
00429                     );
00430                     if ( $num > 0 ) {
00431                         $tpl->set( 'numberofwatchingusers',
00432                             $this->msg( 'number_of_watching_users_pageview' )->numParams( $num )->parse()
00433                         );
00434                     }
00435                 }
00436 
00437                 if ( $wgMaxCredits != 0 ) {
00438                     $tpl->set( 'credits', Action::factory( 'credits', $this->getWikiPage(),
00439                         $this->getContext() )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) );
00440                 } else {
00441                     $tpl->set( 'lastmod', $this->lastModified() );
00442                 }
00443             }
00444             $tpl->set( 'copyright', $this->getCopyright() );
00445         }
00446         wfProfileOut( __METHOD__ . '-stuff3' );
00447 
00448         wfProfileIn( __METHOD__ . '-stuff4' );
00449         $tpl->set( 'copyrightico', $this->getCopyrightIcon() );
00450         $tpl->set( 'poweredbyico', $this->getPoweredBy() );
00451         $tpl->set( 'disclaimer', $this->disclaimerLink() );
00452         $tpl->set( 'privacy', $this->privacyLink() );
00453         $tpl->set( 'about', $this->aboutLink() );
00454 
00455         $tpl->set( 'footerlinks', array(
00456             'info' => array(
00457                 'lastmod',
00458                 'viewcount',
00459                 'numberofwatchingusers',
00460                 'credits',
00461                 'copyright',
00462             ),
00463             'places' => array(
00464                 'privacy',
00465                 'about',
00466                 'disclaimer',
00467             ),
00468         ) );
00469 
00470         global $wgFooterIcons;
00471         $tpl->set( 'footericons', $wgFooterIcons );
00472         foreach ( $tpl->data['footericons'] as $footerIconsKey => &$footerIconsBlock ) {
00473             if ( count( $footerIconsBlock ) > 0 ) {
00474                 foreach ( $footerIconsBlock as &$footerIcon ) {
00475                     if ( isset( $footerIcon['src'] ) ) {
00476                         if ( !isset( $footerIcon['width'] ) ) {
00477                             $footerIcon['width'] = 88;
00478                         }
00479                         if ( !isset( $footerIcon['height'] ) ) {
00480                             $footerIcon['height'] = 31;
00481                         }
00482                     }
00483                 }
00484             } else {
00485                 unset( $tpl->data['footericons'][$footerIconsKey] );
00486             }
00487         }
00488 
00489         $tpl->set( 'sitenotice', $this->getSiteNotice() );
00490         $tpl->set( 'bottomscripts', $this->bottomScripts() );
00491         $tpl->set( 'printfooter', $this->printSource() );
00492 
00493         # An ID that includes the actual body text; without categories, contentSub, ...
00494         $realBodyAttribs = array( 'id' => 'mw-content-text' );
00495 
00496         # Add a mw-content-ltr/rtl class to be able to style based on text direction
00497         # when the content is different from the UI language, i.e.:
00498         # not for special pages or file pages AND only when viewing AND if the page exists
00499         # (or is in MW namespace, because that has default content)
00500         if ( !in_array( $title->getNamespace(), array( NS_SPECIAL, NS_FILE ) ) &&
00501             Action::getActionName( $this ) === 'view' &&
00502             ( $title->exists() || $title->getNamespace() == NS_MEDIAWIKI ) ) {
00503             $pageLang = $title->getPageViewLanguage();
00504             $realBodyAttribs['lang'] = $pageLang->getHtmlCode();
00505             $realBodyAttribs['dir'] = $pageLang->getDir();
00506             $realBodyAttribs['class'] = 'mw-content-' . $pageLang->getDir();
00507         }
00508 
00509         $out->mBodytext = Html::rawElement( 'div', $realBodyAttribs, $out->mBodytext );
00510         $tpl->setRef( 'bodytext', $out->mBodytext );
00511 
00512         $language_urls = $this->getLanguages();
00513         if ( count( $language_urls ) ) {
00514             $tpl->setRef( 'language_urls', $language_urls );
00515         } else {
00516             $tpl->set( 'language_urls', false );
00517         }
00518         wfProfileOut( __METHOD__ . '-stuff4' );
00519 
00520         wfProfileIn( __METHOD__ . '-stuff5' );
00521         # Personal toolbar
00522         $tpl->set( 'personal_urls', $this->buildPersonalUrls() );
00523         $content_navigation = $this->buildContentNavigationUrls();
00524         $content_actions = $this->buildContentActionUrls( $content_navigation );
00525         $tpl->setRef( 'content_navigation', $content_navigation );
00526         $tpl->setRef( 'content_actions', $content_actions );
00527 
00528         $tpl->set( 'sidebar', $this->buildSidebar() );
00529         $tpl->set( 'nav_urls', $this->buildNavUrls() );
00530 
00531         // Set the head scripts near the end, in case the above actions resulted in added scripts
00532         $tpl->set( 'headelement', $out->headElement( $this ) );
00533 
00534         $tpl->set( 'debug', '' );
00535         $tpl->set( 'debughtml', $this->generateDebugHTML() );
00536         $tpl->set( 'reporttime', wfReportTime() );
00537 
00538         // original version by hansm
00539         if ( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) {
00540             wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" );
00541         }
00542 
00543         // Set the bodytext to another key so that skins can just output it on it's own
00544         // and output printfooter and debughtml separately
00545         $tpl->set( 'bodycontent', $tpl->data['bodytext'] );
00546 
00547         // Append printfooter and debughtml onto bodytext so that skins that
00548         // were already using bodytext before they were split out don't suddenly
00549         // start not outputting information.
00550         $tpl->data['bodytext'] .= Html::rawElement(
00551             'div',
00552             array( 'class' => 'printfooter' ),
00553             "\n{$tpl->data['printfooter']}"
00554         ) . "\n";
00555         $tpl->data['bodytext'] .= $tpl->data['debughtml'];
00556 
00557         // allow extensions adding stuff after the page content.
00558         // See Skin::afterContentHook() for further documentation.
00559         $tpl->set( 'dataAfterContent', $this->afterContentHook() );
00560         wfProfileOut( __METHOD__ . '-stuff5' );
00561 
00562         wfProfileOut( __METHOD__ );
00563         return $tpl;
00564     }
00565 
00570     public function getPersonalToolsList() {
00571         $tpl = $this->setupTemplateForOutput();
00572         $tpl->set( 'personal_urls', $this->buildPersonalUrls() );
00573         $html = '';
00574         foreach ( $tpl->getPersonalTools() as $key => $item ) {
00575             $html .= $tpl->makeListItem( $key, $item );
00576         }
00577         return $html;
00578     }
00579 
00588     function formatLanguageName( $name ) {
00589         return $this->getLanguage()->ucfirst( $name );
00590     }
00591 
00600     function printOrError( $str ) {
00601         echo $str;
00602     }
00603 
00613     function useCombinedLoginLink() {
00614         global $wgUseCombinedLoginLink;
00615         return $wgUseCombinedLoginLink;
00616     }
00617 
00622     protected function buildPersonalUrls() {
00623         $title = $this->getTitle();
00624         $request = $this->getRequest();
00625         $pageurl = $title->getLocalURL();
00626         wfProfileIn( __METHOD__ );
00627 
00628         /* set up the default links for the personal toolbar */
00629         $personal_urls = array();
00630 
00631         # Due to bug 32276, if a user does not have read permissions,
00632         # $this->getTitle() will just give Special:Badtitle, which is
00633         # not especially useful as a returnto parameter. Use the title
00634         # from the request instead, if there was one.
00635         if ( $this->getUser()->isAllowed( 'read' ) ) {
00636             $page = $this->getTitle();
00637         } else {
00638             $page = Title::newFromText( $request->getVal( 'title', '' ) );
00639         }
00640         $page = $request->getVal( 'returnto', $page );
00641         $a = array();
00642         if ( strval( $page ) !== '' ) {
00643             $a['returnto'] = $page;
00644             $query = $request->getVal( 'returntoquery', $this->thisquery );
00645             if ( $query != '' ) {
00646                 $a['returntoquery'] = $query;
00647             }
00648         }
00649 
00650         $returnto = wfArrayToCgi( $a );
00651         if ( $this->loggedin ) {
00652             $personal_urls['userpage'] = array(
00653                 'text' => $this->username,
00654                 'href' => &$this->userpageUrlDetails['href'],
00655                 'class' => $this->userpageUrlDetails['exists'] ? false : 'new',
00656                 'active' => ( $this->userpageUrlDetails['href'] == $pageurl ),
00657                 'dir' => 'auto'
00658             );
00659             $usertalkUrlDetails = $this->makeTalkUrlDetails( $this->userpage );
00660             $personal_urls['mytalk'] = array(
00661                 'text' => $this->msg( 'mytalk' )->text(),
00662                 'href' => &$usertalkUrlDetails['href'],
00663                 'class' => $usertalkUrlDetails['exists'] ? false : 'new',
00664                 'active' => ( $usertalkUrlDetails['href'] == $pageurl )
00665             );
00666             $href = self::makeSpecialUrl( 'Preferences' );
00667             $personal_urls['preferences'] = array(
00668                 'text' => $this->msg( 'mypreferences' )->text(),
00669                 'href' => $href,
00670                 'active' => ( $href == $pageurl )
00671             );
00672 
00673             if ( $this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
00674                 $href = self::makeSpecialUrl( 'Watchlist' );
00675                 $personal_urls['watchlist'] = array(
00676                     'text' => $this->msg( 'mywatchlist' )->text(),
00677                     'href' => $href,
00678                     'active' => ( $href == $pageurl )
00679                 );
00680             }
00681 
00682             # We need to do an explicit check for Special:Contributions, as we
00683             # have to match both the title, and the target, which could come
00684             # from request values (Special:Contributions?target=Jimbo_Wales)
00685             # or be specified in "sub page" form
00686             # (Special:Contributions/Jimbo_Wales). The plot
00687             # thickens, because the Title object is altered for special pages,
00688             # so it doesn't contain the original alias-with-subpage.
00689             $origTitle = Title::newFromText( $request->getText( 'title' ) );
00690             if ( $origTitle instanceof Title && $origTitle->isSpecialPage() ) {
00691                 list( $spName, $spPar ) = SpecialPageFactory::resolveAlias( $origTitle->getText() );
00692                 $active = $spName == 'Contributions'
00693                     && ( ( $spPar && $spPar == $this->username )
00694                         || $request->getText( 'target' ) == $this->username );
00695             } else {
00696                 $active = false;
00697             }
00698 
00699             $href = self::makeSpecialUrlSubpage( 'Contributions', $this->username );
00700             $personal_urls['mycontris'] = array(
00701                 'text' => $this->msg( 'mycontris' )->text(),
00702                 'href' => $href,
00703                 'active' => $active
00704             );
00705             $personal_urls['logout'] = array(
00706                 'text' => $this->msg( 'pt-userlogout' )->text(),
00707                 'href' => self::makeSpecialUrl( 'Userlogout',
00708                     // userlogout link must always contain an & character, otherwise we might not be able
00709                     // to detect a buggy precaching proxy (bug 17790)
00710                     $title->isSpecial( 'Preferences' ) ? 'noreturnto' : $returnto
00711                 ),
00712                 'active' => false
00713             );
00714         } else {
00715             $useCombinedLoginLink = $this->useCombinedLoginLink();
00716             $loginlink = $this->getUser()->isAllowed( 'createaccount' ) && $useCombinedLoginLink
00717                 ? 'nav-login-createaccount'
00718                 : 'pt-login';
00719             $is_signup = $request->getText( 'type' ) == 'signup';
00720 
00721             $login_url = array(
00722                 'text' => $this->msg( $loginlink )->text(),
00723                 'href' => self::makeSpecialUrl( 'Userlogin', $returnto ),
00724                 'active' => $title->isSpecial( 'Userlogin' )
00725                     && ( $loginlink == 'nav-login-createaccount' || !$is_signup ),
00726             );
00727             $createaccount_url = array(
00728                 'text' => $this->msg( 'pt-createaccount' )->text(),
00729                 'href' => self::makeSpecialUrl( 'Userlogin', "$returnto&type=signup" ),
00730                 'active' => $title->isSpecial( 'Userlogin' ) && $is_signup,
00731             );
00732 
00733             if ( $this->showIPinHeader() ) {
00734                 $href = &$this->userpageUrlDetails['href'];
00735                 $personal_urls['anonuserpage'] = array(
00736                     'text' => $this->username,
00737                     'href' => $href,
00738                     'class' => $this->userpageUrlDetails['exists'] ? false : 'new',
00739                     'active' => ( $pageurl == $href )
00740                 );
00741                 $usertalkUrlDetails = $this->makeTalkUrlDetails( $this->userpage );
00742                 $href = &$usertalkUrlDetails['href'];
00743                 $personal_urls['anontalk'] = array(
00744                     'text' => $this->msg( 'anontalk' )->text(),
00745                     'href' => $href,
00746                     'class' => $usertalkUrlDetails['exists'] ? false : 'new',
00747                     'active' => ( $pageurl == $href )
00748                 );
00749             }
00750 
00751             if ( $this->getUser()->isAllowed( 'createaccount' ) && !$useCombinedLoginLink ) {
00752                 $personal_urls['createaccount'] = $createaccount_url;
00753             }
00754 
00755             $personal_urls['login'] = $login_url;
00756         }
00757 
00758         wfRunHooks( 'PersonalUrls', array( &$personal_urls, &$title, $this ) );
00759         wfProfileOut( __METHOD__ );
00760         return $personal_urls;
00761     }
00762 
00774     function tabAction( $title, $message, $selected, $query = '', $checkEdit = false ) {
00775         $classes = array();
00776         if ( $selected ) {
00777             $classes[] = 'selected';
00778         }
00779         if ( $checkEdit && !$title->isKnown() ) {
00780             $classes[] = 'new';
00781             if ( $query !== '' ) {
00782                 $query = 'action=edit&redlink=1&' . $query;
00783             } else {
00784                 $query = 'action=edit&redlink=1';
00785             }
00786         }
00787 
00788         // wfMessageFallback will nicely accept $message as an array of fallbacks
00789         // or just a single key
00790         $msg = wfMessageFallback( $message )->setContext( $this->getContext() );
00791         if ( is_array( $message ) ) {
00792             // for hook compatibility just keep the last message name
00793             $message = end( $message );
00794         }
00795         if ( $msg->exists() ) {
00796             $text = $msg->text();
00797         } else {
00798             global $wgContLang;
00799             $text = $wgContLang->getFormattedNsText(
00800                 MWNamespace::getSubject( $title->getNamespace() ) );
00801         }
00802 
00803         $result = array();
00804         if ( !wfRunHooks( 'SkinTemplateTabAction', array( &$this,
00805                 $title, $message, $selected, $checkEdit,
00806                 &$classes, &$query, &$text, &$result ) ) ) {
00807             return $result;
00808         }
00809 
00810         return array(
00811             'class' => implode( ' ', $classes ),
00812             'text' => $text,
00813             'href' => $title->getLocalURL( $query ),
00814             'primary' => true );
00815     }
00816 
00817     function makeTalkUrlDetails( $name, $urlaction = '' ) {
00818         $title = Title::newFromText( $name );
00819         if ( !is_object( $title ) ) {
00820             throw new MWException( __METHOD__ . " given invalid pagename $name" );
00821         }
00822         $title = $title->getTalkPage();
00823         self::checkTitle( $title, $name );
00824         return array(
00825             'href' => $title->getLocalURL( $urlaction ),
00826             'exists' => $title->getArticleID() != 0,
00827         );
00828     }
00829 
00830     function makeArticleUrlDetails( $name, $urlaction = '' ) {
00831         $title = Title::newFromText( $name );
00832         $title = $title->getSubjectPage();
00833         self::checkTitle( $title, $name );
00834         return array(
00835             'href' => $title->getLocalURL( $urlaction ),
00836             'exists' => $title->getArticleID() != 0,
00837         );
00838     }
00839 
00874     protected function buildContentNavigationUrls() {
00875         global $wgDisableLangConversion;
00876 
00877         wfProfileIn( __METHOD__ );
00878 
00879         // Display tabs for the relevant title rather than always the title itself
00880         $title = $this->getRelevantTitle();
00881         $onPage = $title->equals( $this->getTitle() );
00882 
00883         $out = $this->getOutput();
00884         $request = $this->getRequest();
00885         $user = $this->getUser();
00886 
00887         $content_navigation = array(
00888             'namespaces' => array(),
00889             'views' => array(),
00890             'actions' => array(),
00891             'variants' => array()
00892         );
00893 
00894         // parameters
00895         $action = $request->getVal( 'action', 'view' );
00896 
00897         $userCanRead = $title->quickUserCan( 'read', $user );
00898 
00899         $preventActiveTabs = false;
00900         wfRunHooks( 'SkinTemplatePreventOtherActiveTabs', array( &$this, &$preventActiveTabs ) );
00901 
00902         // Checks if page is some kind of content
00903         if ( $title->canExist() ) {
00904             // Gets page objects for the related namespaces
00905             $subjectPage = $title->getSubjectPage();
00906             $talkPage = $title->getTalkPage();
00907 
00908             // Determines if this is a talk page
00909             $isTalk = $title->isTalkPage();
00910 
00911             // Generates XML IDs from namespace names
00912             $subjectId = $title->getNamespaceKey( '' );
00913 
00914             if ( $subjectId == 'main' ) {
00915                 $talkId = 'talk';
00916             } else {
00917                 $talkId = "{$subjectId}_talk";
00918             }
00919 
00920             $skname = $this->skinname;
00921 
00922             // Adds namespace links
00923             $subjectMsg = array( "nstab-$subjectId" );
00924             if ( $subjectPage->isMainPage() ) {
00925                 array_unshift( $subjectMsg, 'mainpage-nstab' );
00926             }
00927             $content_navigation['namespaces'][$subjectId] = $this->tabAction(
00928                 $subjectPage, $subjectMsg, !$isTalk && !$preventActiveTabs, '', $userCanRead
00929             );
00930             $content_navigation['namespaces'][$subjectId]['context'] = 'subject';
00931             $content_navigation['namespaces'][$talkId] = $this->tabAction(
00932                 $talkPage, array( "nstab-$talkId", 'talk' ), $isTalk && !$preventActiveTabs, '', $userCanRead
00933             );
00934             $content_navigation['namespaces'][$talkId]['context'] = 'talk';
00935 
00936             if ( $userCanRead ) {
00937                 $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() &&
00938                     $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal();
00939 
00940                 // Adds view view link
00941                 if ( $title->exists() || $isForeignFile ) {
00942                     $content_navigation['views']['view'] = $this->tabAction(
00943                         $isTalk ? $talkPage : $subjectPage,
00944                         array( "$skname-view-view", 'view' ),
00945                         ( $onPage && ( $action == 'view' || $action == 'purge' ) ), '', true
00946                     );
00947                     // signal to hide this from simple content_actions
00948                     $content_navigation['views']['view']['redundant'] = true;
00949                 }
00950 
00951                 // If it is a non-local file, show a link to the file in its own repository
00952                 if ( $isForeignFile ) {
00953                     $file = $this->getWikiPage()->getFile();
00954                     $content_navigation['views']['view-foreign'] = array(
00955                         'class' => '',
00956                         'text' => wfMessageFallback( "$skname-view-foreign", 'view-foreign' )->
00957                             setContext( $this->getContext() )->
00958                             params( $file->getRepo()->getDisplayName() )->text(),
00959                         'href' => $file->getDescriptionUrl(),
00960                         'primary' => false,
00961                     );
00962                 }
00963 
00964                 wfProfileIn( __METHOD__ . '-edit' );
00965 
00966                 // Checks if user can edit the current page if it exists or create it otherwise
00967                 if ( $title->quickUserCan( 'edit', $user )
00968                     && ( $title->exists() || $title->quickUserCan( 'create', $user ) )
00969                 ) {
00970                     // Builds CSS class for talk page links
00971                     $isTalkClass = $isTalk ? ' istalk' : '';
00972                     // Whether the user is editing the page
00973                     $isEditing = $onPage && ( $action == 'edit' || $action == 'submit' );
00974                     // Whether to show the "Add a new section" tab
00975                     // Checks if this is a current rev of talk page and is not forced to be hidden
00976                     $showNewSection = !$out->forceHideNewSectionLink()
00977                         && ( ( $isTalk && $this->isRevisionCurrent() ) || $out->showNewSectionLink() );
00978                     $section = $request->getVal( 'section' );
00979 
00980                     if ( $title->exists()
00981                         || ( $title->getNamespace() == NS_MEDIAWIKI
00982                             && $title->getDefaultMessageText() !== false
00983                         )
00984                     ) {
00985                         $msgKey = $isForeignFile ? 'edit-local' : 'edit';
00986                     } else {
00987                         $msgKey = $isForeignFile ? 'create-local' : 'create';
00988                     }
00989                     $content_navigation['views']['edit'] = array(
00990                         'class' => ( $isEditing && ( $section !== 'new' || !$showNewSection )
00991                             ? 'selected'
00992                             : ''
00993                         ) . $isTalkClass,
00994                         'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey )
00995                             ->setContext( $this->getContext() )->text(),
00996                         'href' => $title->getLocalURL( $this->editUrlOptions() ),
00997                         'primary' => !$isForeignFile, // don't collapse this in vector
00998                     );
00999 
01000                     // section link
01001                     if ( $showNewSection ) {
01002                         // Adds new section link
01003                         //$content_navigation['actions']['addsection']
01004                         $content_navigation['views']['addsection'] = array(
01005                             'class' => ( $isEditing && $section == 'new' ) ? 'selected' : false,
01006                             'text' => wfMessageFallback( "$skname-action-addsection", 'addsection' )
01007                                 ->setContext( $this->getContext() )->text(),
01008                             'href' => $title->getLocalURL( 'action=edit&section=new' )
01009                         );
01010                     }
01011                 // Checks if the page has some kind of viewable content
01012                 } elseif ( $title->hasSourceText() ) {
01013                     // Adds view source view link
01014                     $content_navigation['views']['viewsource'] = array(
01015                         'class' => ( $onPage && $action == 'edit' ) ? 'selected' : false,
01016                         'text' => wfMessageFallback( "$skname-action-viewsource", 'viewsource' )
01017                             ->setContext( $this->getContext() )->text(),
01018                         'href' => $title->getLocalURL( $this->editUrlOptions() ),
01019                         'primary' => true, // don't collapse this in vector
01020                     );
01021                 }
01022                 wfProfileOut( __METHOD__ . '-edit' );
01023 
01024                 wfProfileIn( __METHOD__ . '-live' );
01025                 // Checks if the page exists
01026                 if ( $title->exists() ) {
01027                     // Adds history view link
01028                     $content_navigation['views']['history'] = array(
01029                         'class' => ( $onPage && $action == 'history' ) ? 'selected' : false,
01030                         'text' => wfMessageFallback( "$skname-view-history", 'history_short' )
01031                             ->setContext( $this->getContext() )->text(),
01032                         'href' => $title->getLocalURL( 'action=history' ),
01033                         'rel' => 'archives',
01034                     );
01035 
01036                     if ( $title->quickUserCan( 'delete', $user ) ) {
01037                         $content_navigation['actions']['delete'] = array(
01038                             'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false,
01039                             'text' => wfMessageFallback( "$skname-action-delete", 'delete' )
01040                                 ->setContext( $this->getContext() )->text(),
01041                             'href' => $title->getLocalURL( 'action=delete' )
01042                         );
01043                     }
01044 
01045                     if ( $title->quickUserCan( 'move', $user ) ) {
01046                         $moveTitle = SpecialPage::getTitleFor( 'Movepage', $title->getPrefixedDBkey() );
01047                         $content_navigation['actions']['move'] = array(
01048                             'class' => $this->getTitle()->isSpecial( 'Movepage' ) ? 'selected' : false,
01049                             'text' => wfMessageFallback( "$skname-action-move", 'move' )
01050                                 ->setContext( $this->getContext() )->text(),
01051                             'href' => $moveTitle->getLocalURL()
01052                         );
01053                     }
01054                 } else {
01055                     // article doesn't exist or is deleted
01056                     if ( $user->isAllowed( 'deletedhistory' ) ) {
01057                         $n = $title->isDeleted();
01058                         if ( $n ) {
01059                             $undelTitle = SpecialPage::getTitleFor( 'Undelete' );
01060                             // If the user can't undelete but can view deleted
01061                             // history show them a "View .. deleted" tab instead.
01062                             $msgKey = $user->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted';
01063                             $content_navigation['actions']['undelete'] = array(
01064                                 'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false,
01065                                 'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" )
01066                                     ->setContext( $this->getContext() )->numParams( $n )->text(),
01067                                 'href' => $undelTitle->getLocalURL( array( 'target' => $title->getPrefixedDBkey() ) )
01068                             );
01069                         }
01070                     }
01071                 }
01072 
01073                 if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() &&
01074                     MWNamespace::getRestrictionLevels( $title->getNamespace(), $user ) !== array( '' )
01075                 ) {
01076                     $mode = $title->isProtected() ? 'unprotect' : 'protect';
01077                     $content_navigation['actions'][$mode] = array(
01078                         'class' => ( $onPage && $action == $mode ) ? 'selected' : false,
01079                         'text' => wfMessageFallback( "$skname-action-$mode", $mode )
01080                             ->setContext( $this->getContext() )->text(),
01081                         'href' => $title->getLocalURL( "action=$mode" )
01082                     );
01083                 }
01084 
01085                 wfProfileOut( __METHOD__ . '-live' );
01086 
01087                 // Checks if the user is logged in
01088                 if ( $this->loggedin && $user->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ) ) {
01098                     $mode = $user->isWatched( $title ) ? 'unwatch' : 'watch';
01099                     $token = WatchAction::getWatchToken( $title, $user, $mode );
01100                     $content_navigation['actions'][$mode] = array(
01101                         'class' => $onPage && ( $action == 'watch' || $action == 'unwatch' ) ? 'selected' : false,
01102                         // uses 'watch' or 'unwatch' message
01103                         'text' => $this->msg( $mode )->text(),
01104                         'href' => $title->getLocalURL( array( 'action' => $mode, 'token' => $token ) )
01105                     );
01106                 }
01107             }
01108 
01109             wfRunHooks( 'SkinTemplateNavigation', array( &$this, &$content_navigation ) );
01110 
01111             if ( $userCanRead && !$wgDisableLangConversion ) {
01112                 $pageLang = $title->getPageLanguage();
01113                 // Gets list of language variants
01114                 $variants = $pageLang->getVariants();
01115                 // Checks that language conversion is enabled and variants exist
01116                 // And if it is not in the special namespace
01117                 if ( count( $variants ) > 1 ) {
01118                     // Gets preferred variant (note that user preference is
01119                     // only possible for wiki content language variant)
01120                     $preferred = $pageLang->getPreferredVariant();
01121                     if ( Action::getActionName( $this ) === 'view' ) {
01122                         $params = $request->getQueryValues();
01123                         unset( $params['title'] );
01124                     } else {
01125                         $params = array();
01126                     }
01127                     // Loops over each variant
01128                     foreach ( $variants as $code ) {
01129                         // Gets variant name from language code
01130                         $varname = $pageLang->getVariantname( $code );
01131                         // Appends variant link
01132                         $content_navigation['variants'][] = array(
01133                             'class' => ( $code == $preferred ) ? 'selected' : false,
01134                             'text' => $varname,
01135                             'href' => $title->getLocalURL( array( 'variant' => $code ) + $params ),
01136                             'lang' => wfBCP47( $code ),
01137                             'hreflang' => wfBCP47( $code ),
01138                         );
01139                     }
01140                 }
01141             }
01142         } else {
01143             // If it's not content, it's got to be a special page
01144             $content_navigation['namespaces']['special'] = array(
01145                 'class' => 'selected',
01146                 'text' => $this->msg( 'nstab-special' )->text(),
01147                 'href' => $request->getRequestURL(), // @see: bug 2457, bug 2510
01148                 'context' => 'subject'
01149             );
01150 
01151             wfRunHooks( 'SkinTemplateNavigation::SpecialPage',
01152                 array( &$this, &$content_navigation ) );
01153         }
01154 
01155         // Equiv to SkinTemplateContentActions
01156         wfRunHooks( 'SkinTemplateNavigation::Universal', array( &$this, &$content_navigation ) );
01157 
01158         // Setup xml ids and tooltip info
01159         foreach ( $content_navigation as $section => &$links ) {
01160             foreach ( $links as $key => &$link ) {
01161                 $xmlID = $key;
01162                 if ( isset( $link['context'] ) && $link['context'] == 'subject' ) {
01163                     $xmlID = 'ca-nstab-' . $xmlID;
01164                 } elseif ( isset( $link['context'] ) && $link['context'] == 'talk' ) {
01165                     $xmlID = 'ca-talk';
01166                 } elseif ( $section == 'variants' ) {
01167                     $xmlID = 'ca-varlang-' . $xmlID;
01168                 } else {
01169                     $xmlID = 'ca-' . $xmlID;
01170                 }
01171                 $link['id'] = $xmlID;
01172             }
01173         }
01174 
01175         # We don't want to give the watch tab an accesskey if the
01176         # page is being edited, because that conflicts with the
01177         # accesskey on the watch checkbox.  We also don't want to
01178         # give the edit tab an accesskey, because that's fairly
01179         # superfluous and conflicts with an accesskey (Ctrl-E) often
01180         # used for editing in Safari.
01181         if ( in_array( $action, array( 'edit', 'submit' ) ) ) {
01182             if ( isset( $content_navigation['views']['edit'] ) ) {
01183                 $content_navigation['views']['edit']['tooltiponly'] = true;
01184             }
01185             if ( isset( $content_navigation['actions']['watch'] ) ) {
01186                 $content_navigation['actions']['watch']['tooltiponly'] = true;
01187             }
01188             if ( isset( $content_navigation['actions']['unwatch'] ) ) {
01189                 $content_navigation['actions']['unwatch']['tooltiponly'] = true;
01190             }
01191         }
01192 
01193         wfProfileOut( __METHOD__ );
01194 
01195         return $content_navigation;
01196     }
01197 
01203     private function buildContentActionUrls( $content_navigation ) {
01204 
01205         wfProfileIn( __METHOD__ );
01206 
01207         // content_actions has been replaced with content_navigation for backwards
01208         // compatibility and also for skins that just want simple tabs content_actions
01209         // is now built by flattening the content_navigation arrays into one
01210 
01211         $content_actions = array();
01212 
01213         foreach ( $content_navigation as $links ) {
01214             foreach ( $links as $key => $value ) {
01215                 if ( isset( $value['redundant'] ) && $value['redundant'] ) {
01216                     // Redundant tabs are dropped from content_actions
01217                     continue;
01218                 }
01219 
01220                 // content_actions used to have ids built using the "ca-$key" pattern
01221                 // so the xmlID based id is much closer to the actual $key that we want
01222                 // for that reason we'll just strip out the ca- if present and use
01223                 // the latter potion of the "id" as the $key
01224                 if ( isset( $value['id'] ) && substr( $value['id'], 0, 3 ) == 'ca-' ) {
01225                     $key = substr( $value['id'], 3 );
01226                 }
01227 
01228                 if ( isset( $content_actions[$key] ) ) {
01229                     wfDebug( __METHOD__ . ": Found a duplicate key for $key while flattening " .
01230                         "content_navigation into content_actions.\n" );
01231                     continue;
01232                 }
01233 
01234                 $content_actions[$key] = $value;
01235             }
01236         }
01237 
01238         wfProfileOut( __METHOD__ );
01239 
01240         return $content_actions;
01241     }
01242 
01247     protected function buildNavUrls() {
01248         global $wgUploadNavigationUrl;
01249 
01250         wfProfileIn( __METHOD__ );
01251 
01252         $out = $this->getOutput();
01253         $request = $this->getRequest();
01254 
01255         $nav_urls = array();
01256         $nav_urls['mainpage'] = array( 'href' => self::makeMainPageUrl() );
01257         if ( $wgUploadNavigationUrl ) {
01258             $nav_urls['upload'] = array( 'href' => $wgUploadNavigationUrl );
01259         } elseif ( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) {
01260             $nav_urls['upload'] = array( 'href' => self::makeSpecialUrl( 'Upload' ) );
01261         } else {
01262             $nav_urls['upload'] = false;
01263         }
01264         $nav_urls['specialpages'] = array( 'href' => self::makeSpecialUrl( 'Specialpages' ) );
01265 
01266         $nav_urls['print'] = false;
01267         $nav_urls['permalink'] = false;
01268         $nav_urls['info'] = false;
01269         $nav_urls['whatlinkshere'] = false;
01270         $nav_urls['recentchangeslinked'] = false;
01271         $nav_urls['contributions'] = false;
01272         $nav_urls['log'] = false;
01273         $nav_urls['blockip'] = false;
01274         $nav_urls['emailuser'] = false;
01275         $nav_urls['userrights'] = false;
01276 
01277         // A print stylesheet is attached to all pages, but nobody ever
01278         // figures that out. :)  Add a link...
01279         if ( !$out->isPrintable() && ( $out->isArticle() || $this->getTitle()->isSpecialPage() ) ) {
01280             $nav_urls['print'] = array(
01281                 'text' => $this->msg( 'printableversion' )->text(),
01282                 'href' => $this->getTitle()->getLocalURL(
01283                     $request->appendQueryValue( 'printable', 'yes', true ) )
01284             );
01285         }
01286 
01287         if ( $out->isArticle() ) {
01288             // Also add a "permalink" while we're at it
01289             $revid = $this->getRevisionId();
01290             if ( $revid ) {
01291                 $nav_urls['permalink'] = array(
01292                     'text' => $this->msg( 'permalink' )->text(),
01293                     'href' => $this->getTitle()->getLocalURL( "oldid=$revid" )
01294                 );
01295             }
01296 
01297             // Use the copy of revision ID in case this undocumented, shady hook tries to mess with internals
01298             wfRunHooks( 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink',
01299                 array( &$this, &$nav_urls, &$revid, &$revid ) );
01300         }
01301 
01302         if ( $out->isArticleRelated() ) {
01303             $nav_urls['whatlinkshere'] = array(
01304                 'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage )->getLocalURL()
01305             );
01306 
01307             $nav_urls['info'] = array(
01308                 'text' => $this->msg( 'pageinfo-toolboxlink' )->text(),
01309                 'href' => $this->getTitle()->getLocalURL( "action=info" )
01310             );
01311 
01312             if ( $this->getTitle()->getArticleID() ) {
01313                 $nav_urls['recentchangeslinked'] = array(
01314                     'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage )->getLocalURL()
01315                 );
01316             }
01317         }
01318 
01319         $user = $this->getRelevantUser();
01320         if ( $user ) {
01321             $rootUser = $user->getName();
01322 
01323             $nav_urls['contributions'] = array(
01324                 'text' => $this->msg( 'contributions', $rootUser )->text(),
01325                 'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser )
01326             );
01327 
01328             $nav_urls['log'] = array(
01329                 'href' => self::makeSpecialUrlSubpage( 'Log', $rootUser )
01330             );
01331 
01332             if ( $this->getUser()->isAllowed( 'block' ) ) {
01333                 $nav_urls['blockip'] = array(
01334                     'text' => $this->msg( 'blockip', $rootUser )->text(),
01335                     'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser )
01336                 );
01337             }
01338 
01339             if ( $this->showEmailUser( $user ) ) {
01340                 $nav_urls['emailuser'] = array(
01341                     'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser )
01342                 );
01343             }
01344 
01345             if ( !$user->isAnon() ) {
01346                 $sur = new UserrightsPage;
01347                 $sur->setContext( $this->getContext() );
01348                 if ( $sur->userCanExecute( $this->getUser() ) ) {
01349                     $nav_urls['userrights'] = array(
01350                         'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
01351                     );
01352                 }
01353             }
01354         }
01355 
01356         wfProfileOut( __METHOD__ );
01357         return $nav_urls;
01358     }
01359 
01364     protected function getNameSpaceKey() {
01365         return $this->getTitle()->getNamespaceKey();
01366     }
01367 }
01368 
01374 abstract class QuickTemplate {
01375 
01377     protected $config;
01378 
01382     function __construct( Config $config = null ) {
01383         $this->data = array();
01384         $this->translator = new MediaWikiI18N();
01385         if ( $config === null ) {
01386             wfDebug( __METHOD__ . ' was called with no Config instance passed to it' );
01387             $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
01388         }
01389         $this->config = $config;
01390     }
01391 
01397     public function set( $name, $value ) {
01398         $this->data[$name] = $value;
01399     }
01400 
01408     public function get( $name, $default = null ) {
01409         if ( isset( $this->data[$name] ) ) {
01410             return $this->data[$name];
01411         } else {
01412             return $default;
01413         }
01414     }
01415 
01420     public function setRef( $name, &$value ) {
01421         $this->data[$name] =& $value;
01422     }
01423 
01427     public function setTranslator( &$t ) {
01428         $this->translator = &$t;
01429     }
01430 
01435     abstract public function execute();
01436 
01442     function text( $str ) {
01443         echo htmlspecialchars( $this->data[$str] );
01444     }
01445 
01451     function html( $str ) {
01452         echo $this->data[$str];
01453     }
01454 
01460     function msg( $str ) {
01461         echo htmlspecialchars( $this->translator->translate( $str ) );
01462     }
01463 
01469     function msgHtml( $str ) {
01470         echo $this->translator->translate( $str );
01471     }
01472 
01479     function msgWiki( $str ) {
01480         global $wgOut;
01481 
01482         $text = $this->translator->translate( $str );
01483         echo $wgOut->parse( $text );
01484     }
01485 
01491     function haveData( $str ) {
01492         return isset( $this->data[$str] );
01493     }
01494 
01501     function haveMsg( $str ) {
01502         $msg = $this->translator->translate( $str );
01503         return ( $msg != '-' ) && ( $msg != '' ); # ????
01504     }
01505 
01511     public function getSkin() {
01512         return $this->data['skin'];
01513     }
01514 
01521     public function getHTML() {
01522         ob_start();
01523         $this->execute();
01524         $html = ob_get_contents();
01525         ob_end_clean();
01526         return $html;
01527     }
01528 }
01529 
01535 abstract class BaseTemplate extends QuickTemplate {
01536 
01543     public function getMsg( $name ) {
01544         return $this->getSkin()->msg( $name );
01545     }
01546 
01547     function msg( $str ) {
01548         echo $this->getMsg( $str )->escaped();
01549     }
01550 
01551     function msgHtml( $str ) {
01552         echo $this->getMsg( $str )->text();
01553     }
01554 
01555     function msgWiki( $str ) {
01556         echo $this->getMsg( $str )->parseAsBlock();
01557     }
01558 
01566     function getToolbox() {
01567         wfProfileIn( __METHOD__ );
01568 
01569         $toolbox = array();
01570         if ( isset( $this->data['nav_urls']['whatlinkshere'] )
01571             && $this->data['nav_urls']['whatlinkshere']
01572         ) {
01573             $toolbox['whatlinkshere'] = $this->data['nav_urls']['whatlinkshere'];
01574             $toolbox['whatlinkshere']['id'] = 't-whatlinkshere';
01575         }
01576         if ( isset( $this->data['nav_urls']['recentchangeslinked'] )
01577             && $this->data['nav_urls']['recentchangeslinked']
01578         ) {
01579             $toolbox['recentchangeslinked'] = $this->data['nav_urls']['recentchangeslinked'];
01580             $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
01581             $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
01582         }
01583         if ( isset( $this->data['feeds'] ) && $this->data['feeds'] ) {
01584             $toolbox['feeds']['id'] = 'feedlinks';
01585             $toolbox['feeds']['links'] = array();
01586             foreach ( $this->data['feeds'] as $key => $feed ) {
01587                 $toolbox['feeds']['links'][$key] = $feed;
01588                 $toolbox['feeds']['links'][$key]['id'] = "feed-$key";
01589                 $toolbox['feeds']['links'][$key]['rel'] = 'alternate';
01590                 $toolbox['feeds']['links'][$key]['type'] = "application/{$key}+xml";
01591                 $toolbox['feeds']['links'][$key]['class'] = 'feedlink';
01592             }
01593         }
01594         foreach ( array( 'contributions', 'log', 'blockip', 'emailuser',
01595             'userrights', 'upload', 'specialpages' ) as $special
01596         ) {
01597             if ( isset( $this->data['nav_urls'][$special] ) && $this->data['nav_urls'][$special] ) {
01598                 $toolbox[$special] = $this->data['nav_urls'][$special];
01599                 $toolbox[$special]['id'] = "t-$special";
01600             }
01601         }
01602         if ( isset( $this->data['nav_urls']['print'] ) && $this->data['nav_urls']['print'] ) {
01603             $toolbox['print'] = $this->data['nav_urls']['print'];
01604             $toolbox['print']['id'] = 't-print';
01605             $toolbox['print']['rel'] = 'alternate';
01606             $toolbox['print']['msg'] = 'printableversion';
01607         }
01608         if ( isset( $this->data['nav_urls']['permalink'] ) && $this->data['nav_urls']['permalink'] ) {
01609             $toolbox['permalink'] = $this->data['nav_urls']['permalink'];
01610             if ( $toolbox['permalink']['href'] === '' ) {
01611                 unset( $toolbox['permalink']['href'] );
01612                 $toolbox['ispermalink']['tooltiponly'] = true;
01613                 $toolbox['ispermalink']['id'] = 't-ispermalink';
01614                 $toolbox['ispermalink']['msg'] = 'permalink';
01615             } else {
01616                 $toolbox['permalink']['id'] = 't-permalink';
01617             }
01618         }
01619         if ( isset( $this->data['nav_urls']['info'] ) && $this->data['nav_urls']['info'] ) {
01620             $toolbox['info'] = $this->data['nav_urls']['info'];
01621             $toolbox['info']['id'] = 't-info';
01622         }
01623 
01624         wfRunHooks( 'BaseTemplateToolbox', array( &$this, &$toolbox ) );
01625         wfProfileOut( __METHOD__ );
01626         return $toolbox;
01627     }
01628 
01639     function getPersonalTools() {
01640         $personal_tools = array();
01641         foreach ( $this->get( 'personal_urls' ) as $key => $plink ) {
01642             # The class on a personal_urls item is meant to go on the <a> instead
01643             # of the <li> so we have to use a single item "links" array instead
01644             # of using most of the personal_url's keys directly.
01645             $ptool = array(
01646                 'links' => array(
01647                     array( 'single-id' => "pt-$key" ),
01648                 ),
01649                 'id' => "pt-$key",
01650             );
01651             if ( isset( $plink['active'] ) ) {
01652                 $ptool['active'] = $plink['active'];
01653             }
01654             foreach ( array( 'href', 'class', 'text', 'dir' ) as $k ) {
01655                 if ( isset( $plink[$k] ) ) {
01656                     $ptool['links'][0][$k] = $plink[$k];
01657                 }
01658             }
01659             $personal_tools[$key] = $ptool;
01660         }
01661         return $personal_tools;
01662     }
01663 
01664     function getSidebar( $options = array() ) {
01665         // Force the rendering of the following portals
01666         $sidebar = $this->data['sidebar'];
01667         if ( !isset( $sidebar['SEARCH'] ) ) {
01668             $sidebar['SEARCH'] = true;
01669         }
01670         if ( !isset( $sidebar['TOOLBOX'] ) ) {
01671             $sidebar['TOOLBOX'] = true;
01672         }
01673         if ( !isset( $sidebar['LANGUAGES'] ) ) {
01674             $sidebar['LANGUAGES'] = true;
01675         }
01676 
01677         if ( !isset( $options['search'] ) || $options['search'] !== true ) {
01678             unset( $sidebar['SEARCH'] );
01679         }
01680         if ( isset( $options['toolbox'] ) && $options['toolbox'] === false ) {
01681             unset( $sidebar['TOOLBOX'] );
01682         }
01683         if ( isset( $options['languages'] ) && $options['languages'] === false ) {
01684             unset( $sidebar['LANGUAGES'] );
01685         }
01686 
01687         $boxes = array();
01688         foreach ( $sidebar as $boxName => $content ) {
01689             if ( $content === false ) {
01690                 continue;
01691             }
01692             switch ( $boxName ) {
01693             case 'SEARCH':
01694                 // Search is a special case, skins should custom implement this
01695                 $boxes[$boxName] = array(
01696                     'id' => 'p-search',
01697                     'header' => $this->getMsg( 'search' )->text(),
01698                     'generated' => false,
01699                     'content' => true,
01700                 );
01701                 break;
01702             case 'TOOLBOX':
01703                 $msgObj = $this->getMsg( 'toolbox' );
01704                 $boxes[$boxName] = array(
01705                     'id' => 'p-tb',
01706                     'header' => $msgObj->exists() ? $msgObj->text() : 'toolbox',
01707                     'generated' => false,
01708                     'content' => $this->getToolbox(),
01709                 );
01710                 break;
01711             case 'LANGUAGES':
01712                 if ( $this->data['language_urls'] ) {
01713                     $msgObj = $this->getMsg( 'otherlanguages' );
01714                     $boxes[$boxName] = array(
01715                         'id' => 'p-lang',
01716                         'header' => $msgObj->exists() ? $msgObj->text() : 'otherlanguages',
01717                         'generated' => false,
01718                         'content' => $this->data['language_urls'],
01719                     );
01720                 }
01721                 break;
01722             default:
01723                 $msgObj = $this->getMsg( $boxName );
01724                 $boxes[$boxName] = array(
01725                     'id' => "p-$boxName",
01726                     'header' => $msgObj->exists() ? $msgObj->text() : $boxName,
01727                     'generated' => true,
01728                     'content' => $content,
01729                 );
01730                 break;
01731             }
01732         }
01733 
01734         // HACK: Compatibility with extensions still using SkinTemplateToolboxEnd
01735         $hookContents = null;
01736         if ( isset( $boxes['TOOLBOX'] ) ) {
01737             ob_start();
01738             // We pass an extra 'true' at the end so extensions using BaseTemplateToolbox
01739             // can abort and avoid outputting double toolbox links
01740             wfRunHooks( 'SkinTemplateToolboxEnd', array( &$this, true ) );
01741             $hookContents = ob_get_contents();
01742             ob_end_clean();
01743             if ( !trim( $hookContents ) ) {
01744                 $hookContents = null;
01745             }
01746         }
01747         // END hack
01748 
01749         if ( isset( $options['htmlOnly'] ) && $options['htmlOnly'] === true ) {
01750             foreach ( $boxes as $boxName => $box ) {
01751                 if ( is_array( $box['content'] ) ) {
01752                     $content = '<ul>';
01753                     foreach ( $box['content'] as $key => $val ) {
01754                         $content .= "\n " . $this->makeListItem( $key, $val );
01755                     }
01756                     // HACK, shove the toolbox end onto the toolbox if we're rendering itself
01757                     if ( $hookContents ) {
01758                         $content .= "\n $hookContents";
01759                     }
01760                     // END hack
01761                     $content .= "\n</ul>\n";
01762                     $boxes[$boxName]['content'] = $content;
01763                 }
01764             }
01765         } else {
01766             if ( $hookContents ) {
01767                 $boxes['TOOLBOXEND'] = array(
01768                     'id' => 'p-toolboxend',
01769                     'header' => $boxes['TOOLBOX']['header'],
01770                     'generated' => false,
01771                     'content' => "<ul>{$hookContents}</ul>",
01772                 );
01773                 // HACK: Make sure that TOOLBOXEND is sorted next to TOOLBOX
01774                 $boxes2 = array();
01775                 foreach ( $boxes as $key => $box ) {
01776                     if ( $key === 'TOOLBOXEND' ) {
01777                         continue;
01778                     }
01779                     $boxes2[$key] = $box;
01780                     if ( $key === 'TOOLBOX' ) {
01781                         $boxes2['TOOLBOXEND'] = $boxes['TOOLBOXEND'];
01782                     }
01783                 }
01784                 $boxes = $boxes2;
01785                 // END hack
01786             }
01787         }
01788 
01789         return $boxes;
01790     }
01791 
01795     protected function renderAfterPortlet( $name ) {
01796         $content = '';
01797         wfRunHooks( 'BaseTemplateAfterPortlet', array( $this, $name, &$content ) );
01798 
01799         if ( $content !== '' ) {
01800             echo "<div class='after-portlet after-portlet-$name'>$content</div>";
01801         }
01802 
01803     }
01804 
01848     function makeLink( $key, $item, $options = array() ) {
01849         if ( isset( $item['text'] ) ) {
01850             $text = $item['text'];
01851         } else {
01852             $text = $this->translator->translate( isset( $item['msg'] ) ? $item['msg'] : $key );
01853         }
01854 
01855         $html = htmlspecialchars( $text );
01856 
01857         if ( isset( $options['text-wrapper'] ) ) {
01858             $wrapper = $options['text-wrapper'];
01859             if ( isset( $wrapper['tag'] ) ) {
01860                 $wrapper = array( $wrapper );
01861             }
01862             while ( count( $wrapper ) > 0 ) {
01863                 $element = array_pop( $wrapper );
01864                 $html = Html::rawElement( $element['tag'], isset( $element['attributes'] )
01865                     ? $element['attributes']
01866                     : null, $html );
01867             }
01868         }
01869 
01870         if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
01871             $attrs = $item;
01872             foreach ( array( 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary' ) as $k ) {
01873                 unset( $attrs[$k] );
01874             }
01875 
01876             if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
01877                 $item['single-id'] = $item['id'];
01878             }
01879             if ( isset( $item['single-id'] ) ) {
01880                 if ( isset( $item['tooltiponly'] ) && $item['tooltiponly'] ) {
01881                     $title = Linker::titleAttrib( $item['single-id'] );
01882                     if ( $title !== false ) {
01883                         $attrs['title'] = $title;
01884                     }
01885                 } else {
01886                     $tip = Linker::tooltipAndAccesskeyAttribs( $item['single-id'] );
01887                     if ( isset( $tip['title'] ) && $tip['title'] !== false ) {
01888                         $attrs['title'] = $tip['title'];
01889                     }
01890                     if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) {
01891                         $attrs['accesskey'] = $tip['accesskey'];
01892                     }
01893                 }
01894             }
01895             if ( isset( $options['link-class'] ) ) {
01896                 if ( isset( $attrs['class'] ) ) {
01897                     $attrs['class'] .= " {$options['link-class']}";
01898                 } else {
01899                     $attrs['class'] = $options['link-class'];
01900                 }
01901             }
01902             $html = Html::rawElement( isset( $attrs['href'] )
01903                 ? 'a'
01904                 : $options['link-fallback'], $attrs, $html );
01905         }
01906 
01907         return $html;
01908     }
01909 
01938     function makeListItem( $key, $item, $options = array() ) {
01939         if ( isset( $item['links'] ) ) {
01940             $links = array();
01941             foreach ( $item['links'] as $linkKey => $link ) {
01942                 $links[] = $this->makeLink( $linkKey, $link, $options );
01943             }
01944             $html = implode( ' ', $links );
01945         } else {
01946             $link = $item;
01947             // These keys are used by makeListItem and shouldn't be passed on to the link
01948             foreach ( array( 'id', 'class', 'active', 'tag', 'itemtitle' ) as $k ) {
01949                 unset( $link[$k] );
01950             }
01951             if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
01952                 // The id goes on the <li> not on the <a> for single links
01953                 // but makeSidebarLink still needs to know what id to use when
01954                 // generating tooltips and accesskeys.
01955                 $link['single-id'] = $item['id'];
01956             }
01957             $html = $this->makeLink( $key, $link, $options );
01958         }
01959 
01960         $attrs = array();
01961         foreach ( array( 'id', 'class' ) as $attr ) {
01962             if ( isset( $item[$attr] ) ) {
01963                 $attrs[$attr] = $item[$attr];
01964             }
01965         }
01966         if ( isset( $item['active'] ) && $item['active'] ) {
01967             if ( !isset( $attrs['class'] ) ) {
01968                 $attrs['class'] = '';
01969             }
01970             $attrs['class'] .= ' active';
01971             $attrs['class'] = trim( $attrs['class'] );
01972         }
01973         if ( isset( $item['itemtitle'] ) ) {
01974             $attrs['title'] = $item['itemtitle'];
01975         }
01976         return Html::rawElement( isset( $options['tag'] ) ? $options['tag'] : 'li', $attrs, $html );
01977     }
01978 
01979     function makeSearchInput( $attrs = array() ) {
01980         $realAttrs = array(
01981             'type' => 'search',
01982             'name' => 'search',
01983             'placeholder' => wfMessage( 'searchsuggest-search' )->text(),
01984             'value' => $this->get( 'search', '' ),
01985         );
01986         $realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
01987         return Html::element( 'input', $realAttrs );
01988     }
01989 
01990     function makeSearchButton( $mode, $attrs = array() ) {
01991         switch ( $mode ) {
01992             case 'go':
01993             case 'fulltext':
01994                 $realAttrs = array(
01995                     'type' => 'submit',
01996                     'name' => $mode,
01997                     'value' => $this->translator->translate(
01998                         $mode == 'go' ? 'searcharticle' : 'searchbutton' ),
01999                 );
02000                 $realAttrs = array_merge(
02001                     $realAttrs,
02002                     Linker::tooltipAndAccesskeyAttribs( "search-$mode" ),
02003                     $attrs
02004                 );
02005                 return Html::element( 'input', $realAttrs );
02006             case 'image':
02007                 $buttonAttrs = array(
02008                     'type' => 'submit',
02009                     'name' => 'button',
02010                 );
02011                 $buttonAttrs = array_merge(
02012                     $buttonAttrs,
02013                     Linker::tooltipAndAccesskeyAttribs( 'search-fulltext' ),
02014                     $attrs
02015                 );
02016                 unset( $buttonAttrs['src'] );
02017                 unset( $buttonAttrs['alt'] );
02018                 unset( $buttonAttrs['width'] );
02019                 unset( $buttonAttrs['height'] );
02020                 $imgAttrs = array(
02021                     'src' => $attrs['src'],
02022                     'alt' => isset( $attrs['alt'] )
02023                         ? $attrs['alt']
02024                         : $this->translator->translate( 'searchbutton' ),
02025                     'width' => isset( $attrs['width'] ) ? $attrs['width'] : null,
02026                     'height' => isset( $attrs['height'] ) ? $attrs['height'] : null,
02027                 );
02028                 return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
02029             default:
02030                 throw new MWException( 'Unknown mode passed to BaseTemplate::makeSearchButton' );
02031         }
02032     }
02033 
02043     function getFooterLinks( $option = null ) {
02044         $footerlinks = $this->get( 'footerlinks' );
02045 
02046         // Reduce footer links down to only those which are being used
02047         $validFooterLinks = array();
02048         foreach ( $footerlinks as $category => $links ) {
02049             $validFooterLinks[$category] = array();
02050             foreach ( $links as $link ) {
02051                 if ( isset( $this->data[$link] ) && $this->data[$link] ) {
02052                     $validFooterLinks[$category][] = $link;
02053                 }
02054             }
02055             if ( count( $validFooterLinks[$category] ) <= 0 ) {
02056                 unset( $validFooterLinks[$category] );
02057             }
02058         }
02059 
02060         if ( $option == 'flat' ) {
02061             // fold footerlinks into a single array using a bit of trickery
02062             $validFooterLinks = call_user_func_array(
02063                 'array_merge',
02064                 array_values( $validFooterLinks )
02065             );
02066         }
02067 
02068         return $validFooterLinks;
02069     }
02070 
02083     function getFooterIcons( $option = null ) {
02084         // Generate additional footer icons
02085         $footericons = $this->get( 'footericons' );
02086 
02087         if ( $option == 'icononly' ) {
02088             // Unset any icons which don't have an image
02089             foreach ( $footericons as &$footerIconsBlock ) {
02090                 foreach ( $footerIconsBlock as $footerIconKey => $footerIcon ) {
02091                     if ( !is_string( $footerIcon ) && !isset( $footerIcon['src'] ) ) {
02092                         unset( $footerIconsBlock[$footerIconKey] );
02093                     }
02094                 }
02095             }
02096             // Redo removal of any empty blocks
02097             foreach ( $footericons as $footerIconsKey => &$footerIconsBlock ) {
02098                 if ( count( $footerIconsBlock ) <= 0 ) {
02099                     unset( $footericons[$footerIconsKey] );
02100                 }
02101             }
02102         } elseif ( $option == 'nocopyright' ) {
02103             unset( $footericons['copyright']['copyright'] );
02104             if ( count( $footericons['copyright'] ) <= 0 ) {
02105                 unset( $footericons['copyright'] );
02106             }
02107         }
02108 
02109         return $footericons;
02110     }
02111 
02117     function printTrail() { ?>
02118 <?php echo MWDebug::getDebugHTML( $this->getSkin()->getContext() ); ?>
02119 <?php $this->html( 'bottomscripts' ); /* JS call to runBodyOnloadHook */ ?>
02120 <?php $this->html( 'reporttime' ) ?>
02121 <?php
02122     }
02123 }