MediaWiki  REL1_23
Skin.php
Go to the documentation of this file.
00001 <?php
00035 abstract class Skin extends ContextSource {
00036     protected $skinname = 'standard';
00037     protected $mRelevantTitle = null;
00038     protected $mRelevantUser = null;
00039 
00044     static function getSkinNames() {
00045         global $wgValidSkinNames;
00046         static $skinsInitialised = false;
00047 
00048         if ( !$skinsInitialised || !count( $wgValidSkinNames ) ) {
00049             # Get a list of available skins
00050             # Build using the regular expression '^(.*).php$'
00051             # Array keys are all lower case, array value keep the case used by filename
00052             #
00053             wfProfileIn( __METHOD__ . '-init' );
00054 
00055             global $wgStyleDirectory;
00056 
00057             $skinDir = dir( $wgStyleDirectory );
00058 
00059             if ( $skinDir !== false && $skinDir !== null ) {
00060                 # while code from www.php.net
00061                 while ( false !== ( $file = $skinDir->read() ) ) {
00062                     // Skip non-PHP files, hidden files, and '.dep' includes
00063                     $matches = array();
00064 
00065                     if ( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
00066                         $aSkin = $matches[1];
00067 
00068                         // We're still loading core skins via the autodiscovery mechanism... :(
00069                         if ( !in_array( $aSkin, array( 'CologneBlue', 'Modern', 'MonoBook', 'Vector' ) ) ) {
00070                             wfLogWarning(
00071                                 "A skin using autodiscovery mechanism, $aSkin, was found in your skins/ directory. " .
00072                                 "The mechanism will be removed in MediaWiki 1.25 and the skin will no longer be recognized. " .
00073                                 "See https://www.mediawiki.org/wiki/Manual:Skin_autodiscovery for information how to fix this."
00074                             );
00075                         }
00076 
00077                         $wgValidSkinNames[strtolower( $aSkin )] = $aSkin;
00078                     }
00079                 }
00080                 $skinDir->close();
00081             }
00082             $skinsInitialised = true;
00083             wfProfileOut( __METHOD__ . '-init' );
00084         }
00085         return $wgValidSkinNames;
00086     }
00087 
00092     static function getSkinNameMessages() {
00093         $messages = array();
00094         foreach ( self::getSkinNames() as $skinKey => $skinName ) {
00095             // Messages: skinname-cologneblue, skinname-monobook, skinname-modern, skinname-vector
00096             $messages[] = "skinname-$skinKey";
00097         }
00098         return $messages;
00099     }
00100 
00108     public static function getAllowedSkins() {
00109         global $wgSkipSkins;
00110 
00111         $allowedSkins = self::getSkinNames();
00112 
00113         foreach ( $wgSkipSkins as $skip ) {
00114             unset( $allowedSkins[$skip] );
00115         }
00116 
00117         return $allowedSkins;
00118     }
00119 
00124     public static function getUsableSkins() {
00125         wfDeprecated( __METHOD__, '1.23' );
00126         return self::getAllowedSkins();
00127     }
00128 
00136     static function normalizeKey( $key ) {
00137         global $wgDefaultSkin;
00138 
00139         $skinNames = Skin::getSkinNames();
00140 
00141         if ( $key == '' || $key == 'default' ) {
00142             // Don't return the default immediately;
00143             // in a misconfiguration we need to fall back.
00144             $key = $wgDefaultSkin;
00145         }
00146 
00147         if ( isset( $skinNames[$key] ) ) {
00148             return $key;
00149         }
00150 
00151         // Older versions of the software used a numeric setting
00152         // in the user preferences.
00153         $fallback = array(
00154             0 => $wgDefaultSkin,
00155             2 => 'cologneblue'
00156         );
00157 
00158         if ( isset( $fallback[$key] ) ) {
00159             $key = $fallback[$key];
00160         }
00161 
00162         if ( isset( $skinNames[$key] ) ) {
00163             return $key;
00164         } elseif ( isset( $skinNames[$wgDefaultSkin] ) ) {
00165             return $wgDefaultSkin;
00166         } else {
00167             return 'vector';
00168         }
00169     }
00170 
00176     static function &newFromKey( $key ) {
00177         global $wgStyleDirectory;
00178 
00179         $key = Skin::normalizeKey( $key );
00180 
00181         $skinNames = Skin::getSkinNames();
00182         $skinName = $skinNames[$key];
00183         $className = "Skin{$skinName}";
00184 
00185         # Grab the skin class and initialise it.
00186         if ( !class_exists( $className ) ) {
00187 
00188             require_once "{$wgStyleDirectory}/{$skinName}.php";
00189 
00190             # Check if we got if not fallback to default skin
00191             if ( !class_exists( $className ) ) {
00192                 # DO NOT die if the class isn't found. This breaks maintenance
00193                 # scripts and can cause a user account to be unrecoverable
00194                 # except by SQL manipulation if a previously valid skin name
00195                 # is no longer valid.
00196                 wfDebug( "Skin class does not exist: $className\n" );
00197                 $className = 'SkinVector';
00198                 require_once "{$wgStyleDirectory}/Vector.php";
00199             }
00200         }
00201         $skin = new $className( $key );
00202         return $skin;
00203     }
00204 
00208     public function getSkinName() {
00209         return $this->skinname;
00210     }
00211 
00215     function initPage( OutputPage $out ) {
00216         wfProfileIn( __METHOD__ );
00217 
00218         $this->preloadExistence();
00219 
00220         wfProfileOut( __METHOD__ );
00221     }
00222 
00229     public function getDefaultModules() {
00230         global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
00231             $wgAjaxWatch, $wgEnableAPI, $wgEnableWriteAPI;
00232 
00233         $out = $this->getOutput();
00234         $user = $out->getUser();
00235         $modules = array(
00236             // modules that enhance the page content in some way
00237             'content' => array(
00238                 'mediawiki.page.ready',
00239             ),
00240             // modules that exist for legacy reasons
00241             'legacy' => array(),
00242             // modules relating to search functionality
00243             'search' => array(),
00244             // modules relating to functionality relating to watching an article
00245             'watch' => array(),
00246             // modules which relate to the current users preferences
00247             'user' => array(),
00248         );
00249         if ( $wgIncludeLegacyJavaScript ) {
00250             $modules['legacy'][] = 'mediawiki.legacy.wikibits';
00251         }
00252 
00253         if ( $wgPreloadJavaScriptMwUtil ) {
00254             $modules['legacy'][] = 'mediawiki.util';
00255         }
00256 
00257         // Add various resources if required
00258         if ( $wgUseAjax ) {
00259             $modules['legacy'][] = 'mediawiki.legacy.ajax';
00260 
00261             if ( $wgEnableAPI ) {
00262                 if ( $wgEnableWriteAPI && $wgAjaxWatch && $user->isLoggedIn()
00263                     && $user->isAllowed( 'writeapi' )
00264                 ) {
00265                     $modules['watch'][] = 'mediawiki.page.watch.ajax';
00266                 }
00267 
00268                 $modules['search'][] = 'mediawiki.searchSuggest';
00269             }
00270         }
00271 
00272         if ( $user->getBoolOption( 'editsectiononrightclick' ) ) {
00273             $modules['user'][] = 'mediawiki.action.view.rightClickEdit';
00274         }
00275 
00276         // Crazy edit-on-double-click stuff
00277         if ( $out->isArticle() && $user->getOption( 'editondblclick' ) ) {
00278             $modules['user'][] = 'mediawiki.action.view.dblClickEdit';
00279         }
00280         return $modules;
00281     }
00282 
00286     function preloadExistence() {
00287         $user = $this->getUser();
00288 
00289         // User/talk link
00290         $titles = array( $user->getUserPage(), $user->getTalkPage() );
00291 
00292         // Other tab link
00293         if ( $this->getTitle()->isSpecialPage() ) {
00294             // nothing
00295         } elseif ( $this->getTitle()->isTalkPage() ) {
00296             $titles[] = $this->getTitle()->getSubjectPage();
00297         } else {
00298             $titles[] = $this->getTitle()->getTalkPage();
00299         }
00300 
00301         $lb = new LinkBatch( $titles );
00302         $lb->setCaller( __METHOD__ );
00303         $lb->execute();
00304     }
00305 
00311     public function getRevisionId() {
00312         return $this->getOutput()->getRevisionId();
00313     }
00314 
00320     public function isRevisionCurrent() {
00321         $revID = $this->getRevisionId();
00322         return $revID == 0 || $revID == $this->getTitle()->getLatestRevID();
00323     }
00324 
00330     public function setRelevantTitle( $t ) {
00331         $this->mRelevantTitle = $t;
00332     }
00333 
00344     public function getRelevantTitle() {
00345         if ( isset( $this->mRelevantTitle ) ) {
00346             return $this->mRelevantTitle;
00347         }
00348         return $this->getTitle();
00349     }
00350 
00356     public function setRelevantUser( $u ) {
00357         $this->mRelevantUser = $u;
00358     }
00359 
00368     public function getRelevantUser() {
00369         if ( isset( $this->mRelevantUser ) ) {
00370             return $this->mRelevantUser;
00371         }
00372         $title = $this->getRelevantTitle();
00373         if ( $title->hasSubjectNamespace( NS_USER ) ) {
00374             $rootUser = $title->getRootText();
00375             if ( User::isIP( $rootUser ) ) {
00376                 $this->mRelevantUser = User::newFromName( $rootUser, false );
00377             } else {
00378                 $user = User::newFromName( $rootUser, false );
00379                 if ( $user && $user->isLoggedIn() ) {
00380                     $this->mRelevantUser = $user;
00381                 }
00382             }
00383             return $this->mRelevantUser;
00384         }
00385         return null;
00386     }
00387 
00392     abstract function outputPage( OutputPage $out = null );
00393 
00398     static function makeVariablesScript( $data ) {
00399         if ( $data ) {
00400             return Html::inlineScript(
00401                 ResourceLoader::makeLoaderConditionalScript( ResourceLoader::makeConfigSetScript( $data ) )
00402             );
00403         } else {
00404             return '';
00405         }
00406     }
00407 
00415     public static function makeGlobalVariablesScript( $unused ) {
00416         global $wgOut;
00417 
00418         wfDeprecated( __METHOD__, '1.19' );
00419 
00420         return self::makeVariablesScript( $wgOut->getJSVars() );
00421     }
00422 
00428     public static function getDynamicStylesheetQuery() {
00429         global $wgSquidMaxage;
00430 
00431         return array(
00432                 'action' => 'raw',
00433                 'maxage' => $wgSquidMaxage,
00434                 'usemsgcache' => 'yes',
00435                 'ctype' => 'text/css',
00436                 'smaxage' => $wgSquidMaxage,
00437             );
00438     }
00439 
00448     abstract function setupSkinUserCss( OutputPage $out );
00449 
00455     function getPageClasses( $title ) {
00456         $numeric = 'ns-' . $title->getNamespace();
00457 
00458         if ( $title->isSpecialPage() ) {
00459             $type = 'ns-special';
00460             // bug 23315: provide a class based on the canonical special page name without subpages
00461             list( $canonicalName ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
00462             if ( $canonicalName ) {
00463                 $type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" );
00464             } else {
00465                 $type .= ' mw-invalidspecialpage';
00466             }
00467         } elseif ( $title->isTalkPage() ) {
00468             $type = 'ns-talk';
00469         } else {
00470             $type = 'ns-subject';
00471         }
00472 
00473         $name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
00474 
00475         return "$numeric $type $name";
00476     }
00477 
00478     /*
00479      * Return values for <html> element
00480      * @return array of associative name-to-value elements for <html> element
00481      */
00482     public function getHtmlElementAttributes() {
00483         $lang = $this->getLanguage();
00484         return array(
00485             'lang' => $lang->getHtmlCode(),
00486             'dir' => $lang->getDir(),
00487             'class' => 'client-nojs',
00488         );
00489     }
00490 
00498     function addToBodyAttributes( $out, &$bodyAttrs ) {
00499         // does nothing by default
00500     }
00501 
00506     function getLogo() {
00507         global $wgLogo;
00508         return $wgLogo;
00509     }
00510 
00514     function getCategoryLinks() {
00515         global $wgUseCategoryBrowser;
00516 
00517         $out = $this->getOutput();
00518         $allCats = $out->getCategoryLinks();
00519 
00520         if ( !count( $allCats ) ) {
00521             return '';
00522         }
00523 
00524         $embed = "<li>";
00525         $pop = "</li>";
00526 
00527         $s = '';
00528         $colon = $this->msg( 'colon-separator' )->escaped();
00529 
00530         if ( !empty( $allCats['normal'] ) ) {
00531             $t = $embed . implode( "{$pop}{$embed}", $allCats['normal'] ) . $pop;
00532 
00533             $msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) )->escaped();
00534             $linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text();
00535             $s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
00536                 Linker::link( Title::newFromText( $linkPage ), $msg )
00537                 . $colon . '<ul>' . $t . '</ul>' . '</div>';
00538         }
00539 
00540         # Hidden categories
00541         if ( isset( $allCats['hidden'] ) ) {
00542             if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) {
00543                 $class = ' mw-hidden-cats-user-shown';
00544             } elseif ( $this->getTitle()->getNamespace() == NS_CATEGORY ) {
00545                 $class = ' mw-hidden-cats-ns-shown';
00546             } else {
00547                 $class = ' mw-hidden-cats-hidden';
00548             }
00549 
00550             $s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
00551                 $this->msg( 'hidden-categories' )->numParams( count( $allCats['hidden'] ) )->escaped() .
00552                 $colon . '<ul>' . $embed . implode( "{$pop}{$embed}", $allCats['hidden'] ) . $pop . '</ul>' .
00553                 '</div>';
00554         }
00555 
00556         # optional 'dmoz-like' category browser. Will be shown under the list
00557         # of categories an article belong to
00558         if ( $wgUseCategoryBrowser ) {
00559             $s .= '<br /><hr />';
00560 
00561             # get a big array of the parents tree
00562             $parenttree = $this->getTitle()->getParentCategoryTree();
00563             # Skin object passed by reference cause it can not be
00564             # accessed under the method subfunction drawCategoryBrowser
00565             $tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree ) );
00566             # Clean out bogus first entry and sort them
00567             unset( $tempout[0] );
00568             asort( $tempout );
00569             # Output one per line
00570             $s .= implode( "<br />\n", $tempout );
00571         }
00572 
00573         return $s;
00574     }
00575 
00581     function drawCategoryBrowser( $tree ) {
00582         $return = '';
00583 
00584         foreach ( $tree as $element => $parent ) {
00585             if ( empty( $parent ) ) {
00586                 # element start a new list
00587                 $return .= "\n";
00588             } else {
00589                 # grab the others elements
00590                 $return .= $this->drawCategoryBrowser( $parent ) . ' &gt; ';
00591             }
00592 
00593             # add our current element to the list
00594             $eltitle = Title::newFromText( $element );
00595             $return .= Linker::link( $eltitle, htmlspecialchars( $eltitle->getText() ) );
00596         }
00597 
00598         return $return;
00599     }
00600 
00604     function getCategories() {
00605         $out = $this->getOutput();
00606 
00607         $catlinks = $this->getCategoryLinks();
00608 
00609         $classes = 'catlinks';
00610 
00611         // Check what we're showing
00612         $allCats = $out->getCategoryLinks();
00613         $showHidden = $this->getUser()->getBoolOption( 'showhiddencats' ) ||
00614                         $this->getTitle()->getNamespace() == NS_CATEGORY;
00615 
00616         if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
00617             $classes .= ' catlinks-allhidden';
00618         }
00619 
00620         return "<div id='catlinks' class='$classes'>{$catlinks}</div>";
00621     }
00622 
00637     protected function afterContentHook() {
00638         $data = '';
00639 
00640         if ( wfRunHooks( 'SkinAfterContent', array( &$data, $this ) ) ) {
00641             // adding just some spaces shouldn't toggle the output
00642             // of the whole <div/>, so we use trim() here
00643             if ( trim( $data ) != '' ) {
00644                 // Doing this here instead of in the skins to
00645                 // ensure that the div has the same ID in all
00646                 // skins
00647                 $data = "<div id='mw-data-after-content'>\n" .
00648                     "\t$data\n" .
00649                     "</div>\n";
00650             }
00651         } else {
00652             wfDebug( "Hook SkinAfterContent changed output processing.\n" );
00653         }
00654 
00655         return $data;
00656     }
00657 
00663     protected function generateDebugHTML() {
00664         return MWDebug::getHTMLDebugLog();
00665     }
00666 
00672     function bottomScripts() {
00673         // TODO and the suckage continues. This function is really just a wrapper around
00674         // OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned
00675         // up at some point
00676         $bottomScriptText = $this->getOutput()->getBottomScripts();
00677         wfRunHooks( 'SkinAfterBottomScripts', array( $this, &$bottomScriptText ) );
00678 
00679         return $bottomScriptText;
00680     }
00681 
00688     function printSource() {
00689         $oldid = $this->getRevisionId();
00690         if ( $oldid ) {
00691             $url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL( 'oldid=' . $oldid ) ) );
00692         } else {
00693             // oldid not available for non existing pages
00694             $url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL() ) );
00695         }
00696         return $this->msg( 'retrievedfrom', '<a href="' . $url . '">' . $url . '</a>' )->text();
00697     }
00698 
00702     function getUndeleteLink() {
00703         $action = $this->getRequest()->getVal( 'action', 'view' );
00704 
00705         if ( $this->getUser()->isAllowed( 'deletedhistory' ) &&
00706             ( $this->getTitle()->getArticleID() == 0 || $action == 'history' ) ) {
00707             $n = $this->getTitle()->isDeleted();
00708 
00709             if ( $n ) {
00710                 if ( $this->getUser()->isAllowed( 'undelete' ) ) {
00711                     $msg = 'thisisdeleted';
00712                 } else {
00713                     $msg = 'viewdeleted';
00714                 }
00715 
00716                 return $this->msg( $msg )->rawParams(
00717                     Linker::linkKnown(
00718                         SpecialPage::getTitleFor( 'Undelete', $this->getTitle()->getPrefixedDBkey() ),
00719                         $this->msg( 'restorelink' )->numParams( $n )->escaped() )
00720                     )->text();
00721             }
00722         }
00723 
00724         return '';
00725     }
00726 
00730     function subPageSubtitle() {
00731         global $wgLang;
00732         $out = $this->getOutput();
00733         $subpages = '';
00734 
00735         if ( !wfRunHooks( 'SkinSubPageSubtitle', array( &$subpages, $this, $out ) ) ) {
00736             return $subpages;
00737         }
00738 
00739         if ( $out->isArticle() && MWNamespace::hasSubpages( $out->getTitle()->getNamespace() ) ) {
00740             $ptext = $this->getTitle()->getPrefixedText();
00741             if ( preg_match( '/\//', $ptext ) ) {
00742                 $links = explode( '/', $ptext );
00743                 array_pop( $links );
00744                 $c = 0;
00745                 $growinglink = '';
00746                 $display = '';
00747 
00748                 foreach ( $links as $link ) {
00749                     $growinglink .= $link;
00750                     $display .= $link;
00751                     $linkObj = Title::newFromText( $growinglink );
00752 
00753                     if ( is_object( $linkObj ) && $linkObj->isKnown() ) {
00754                         $getlink = Linker::linkKnown(
00755                             $linkObj,
00756                             htmlspecialchars( $display )
00757                         );
00758 
00759                         $c++;
00760 
00761                         if ( $c > 1 ) {
00762                             $subpages .= $wgLang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
00763                         } else {
00764                             $subpages .= '&lt; ';
00765                         }
00766 
00767                         $subpages .= $getlink;
00768                         $display = '';
00769                     } else {
00770                         $display .= '/';
00771                     }
00772                     $growinglink .= '/';
00773                 }
00774             }
00775         }
00776 
00777         return $subpages;
00778     }
00779 
00784     function showIPinHeader() {
00785         global $wgShowIPinHeader;
00786         return $wgShowIPinHeader && session_id() != '';
00787     }
00788 
00792     function getSearchLink() {
00793         $searchPage = SpecialPage::getTitleFor( 'Search' );
00794         return $searchPage->getLocalURL();
00795     }
00796 
00800     function escapeSearchLink() {
00801         return htmlspecialchars( $this->getSearchLink() );
00802     }
00803 
00808     function getCopyright( $type = 'detect' ) {
00809         global $wgRightsPage, $wgRightsUrl, $wgRightsText, $wgContLang;
00810 
00811         if ( $type == 'detect' ) {
00812             if ( !$this->isRevisionCurrent() && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled() ) {
00813                 $type = 'history';
00814             } else {
00815                 $type = 'normal';
00816             }
00817         }
00818 
00819         if ( $type == 'history' ) {
00820             $msg = 'history_copyright';
00821         } else {
00822             $msg = 'copyright';
00823         }
00824 
00825         if ( $wgRightsPage ) {
00826             $title = Title::newFromText( $wgRightsPage );
00827             $link = Linker::linkKnown( $title, $wgRightsText );
00828         } elseif ( $wgRightsUrl ) {
00829             $link = Linker::makeExternalLink( $wgRightsUrl, $wgRightsText );
00830         } elseif ( $wgRightsText ) {
00831             $link = $wgRightsText;
00832         } else {
00833             # Give up now
00834             return '';
00835         }
00836 
00837         // Allow for site and per-namespace customization of copyright notice.
00838         $forContent = true;
00839 
00840         wfRunHooks( 'SkinCopyrightFooter', array( $this->getTitle(), $type, &$msg, &$link, &$forContent ) );
00841 
00842         $msgObj = $this->msg( $msg )->rawParams( $link );
00843         if ( $forContent ) {
00844             $msg = $msgObj->inContentLanguage()->text();
00845             if ( $this->getLanguage()->getCode() !== $wgContLang->getCode() ) {
00846                 $msg = Html::rawElement( 'span', array( 'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ), $msg );
00847             }
00848             return $msg;
00849         } else {
00850             return $msgObj->text();
00851         }
00852     }
00853 
00857     function getCopyrightIcon() {
00858         global $wgRightsUrl, $wgRightsText, $wgRightsIcon, $wgCopyrightIcon;
00859 
00860         $out = '';
00861 
00862         if ( isset( $wgCopyrightIcon ) && $wgCopyrightIcon ) {
00863             $out = $wgCopyrightIcon;
00864         } elseif ( $wgRightsIcon ) {
00865             $icon = htmlspecialchars( $wgRightsIcon );
00866 
00867             if ( $wgRightsUrl ) {
00868                 $url = htmlspecialchars( $wgRightsUrl );
00869                 $out .= '<a href="' . $url . '">';
00870             }
00871 
00872             $text = htmlspecialchars( $wgRightsText );
00873             $out .= "<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
00874 
00875             if ( $wgRightsUrl ) {
00876                 $out .= '</a>';
00877             }
00878         }
00879 
00880         return $out;
00881     }
00882 
00887     function getPoweredBy() {
00888         global $wgStylePath;
00889 
00890         $url = htmlspecialchars( "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" );
00891         $text = '<a href="//www.mediawiki.org/"><img src="' . $url . '" height="31" width="88" alt="Powered by MediaWiki" /></a>';
00892         wfRunHooks( 'SkinGetPoweredBy', array( &$text, $this ) );
00893         return $text;
00894     }
00895 
00901     protected function lastModified() {
00902         $timestamp = $this->getOutput()->getRevisionTimestamp();
00903 
00904         # No cached timestamp, load it from the database
00905         if ( $timestamp === null ) {
00906             $timestamp = Revision::getTimestampFromId( $this->getTitle(), $this->getRevisionId() );
00907         }
00908 
00909         if ( $timestamp ) {
00910             $d = $this->getLanguage()->userDate( $timestamp, $this->getUser() );
00911             $t = $this->getLanguage()->userTime( $timestamp, $this->getUser() );
00912             $s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->text();
00913         } else {
00914             $s = '';
00915         }
00916 
00917         if ( wfGetLB()->getLaggedSlaveMode() ) {
00918             $s .= ' <strong>' . $this->msg( 'laggedslavemode' )->text() . '</strong>';
00919         }
00920 
00921         return $s;
00922     }
00923 
00928     function logoText( $align = '' ) {
00929         if ( $align != '' ) {
00930             $a = " style='float: {$align};'";
00931         } else {
00932             $a = '';
00933         }
00934 
00935         $mp = $this->msg( 'mainpage' )->escaped();
00936         $mptitle = Title::newMainPage();
00937         $url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) : '' );
00938 
00939         $logourl = $this->getLogo();
00940         $s = "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
00941 
00942         return $s;
00943     }
00944 
00951     function makeFooterIcon( $icon, $withImage = 'withImage' ) {
00952         if ( is_string( $icon ) ) {
00953             $html = $icon;
00954         } else { // Assuming array
00955             $url = isset( $icon["url"] ) ? $icon["url"] : null;
00956             unset( $icon["url"] );
00957             if ( isset( $icon["src"] ) && $withImage === 'withImage' ) {
00958                 $html = Html::element( 'img', $icon ); // do this the lazy way, just pass icon data as an attribute array
00959             } else {
00960                 $html = htmlspecialchars( $icon["alt"] );
00961             }
00962             if ( $url ) {
00963                 $html = Html::rawElement( 'a', array( "href" => $url ), $html );
00964             }
00965         }
00966         return $html;
00967     }
00968 
00973     function mainPageLink() {
00974         $s = Linker::linkKnown(
00975             Title::newMainPage(),
00976             $this->msg( 'mainpage' )->escaped()
00977         );
00978 
00979         return $s;
00980     }
00981 
00988     public function footerLink( $desc, $page ) {
00989         // if the link description has been set to "-" in the default language,
00990         if ( $this->msg( $desc )->inContentLanguage()->isDisabled() ) {
00991             // then it is disabled, for all languages.
00992             return '';
00993         } else {
00994             // Otherwise, we display the link for the user, described in their
00995             // language (which may or may not be the same as the default language),
00996             // but we make the link target be the one site-wide page.
00997             $title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() );
00998 
00999             return Linker::linkKnown(
01000                 $title,
01001                 $this->msg( $desc )->escaped()
01002             );
01003         }
01004     }
01005 
01010     function privacyLink() {
01011         return $this->footerLink( 'privacy', 'privacypage' );
01012     }
01013 
01018     function aboutLink() {
01019         return $this->footerLink( 'aboutsite', 'aboutpage' );
01020     }
01021 
01026     function disclaimerLink() {
01027         return $this->footerLink( 'disclaimers', 'disclaimerpage' );
01028     }
01029 
01037     function editUrlOptions() {
01038         $options = array( 'action' => 'edit' );
01039 
01040         if ( !$this->isRevisionCurrent() ) {
01041             $options['oldid'] = intval( $this->getRevisionId() );
01042         }
01043 
01044         return $options;
01045     }
01046 
01051     function showEmailUser( $id ) {
01052         if ( $id instanceof User ) {
01053             $targetUser = $id;
01054         } else {
01055             $targetUser = User::newFromId( $id );
01056         }
01057         return $this->getUser()->canSendEmail() && # the sending user must have a confirmed email address
01058             $targetUser->canReceiveEmail(); # the target user must have a confirmed email address and allow emails from users
01059     }
01060 
01068     function getCommonStylePath( $name ) {
01069         global $wgStylePath, $wgStyleVersion;
01070         return "$wgStylePath/common/$name?$wgStyleVersion";
01071     }
01072 
01080     function getSkinStylePath( $name ) {
01081         global $wgStylePath, $wgStyleVersion;
01082         return "$wgStylePath/{$this->stylename}/$name?$wgStyleVersion";
01083     }
01084 
01085     /* these are used extensively in SkinTemplate, but also some other places */
01086 
01091     static function makeMainPageUrl( $urlaction = '' ) {
01092         $title = Title::newMainPage();
01093         self::checkTitle( $title, '' );
01094 
01095         return $title->getLocalURL( $urlaction );
01096     }
01097 
01109     static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) {
01110         $title = SpecialPage::getSafeTitleFor( $name );
01111         if ( is_null( $proto ) ) {
01112             return $title->getLocalURL( $urlaction );
01113         } else {
01114             return $title->getFullURL( $urlaction, false, $proto );
01115         }
01116     }
01117 
01124     static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
01125         $title = SpecialPage::getSafeTitleFor( $name, $subpage );
01126         return $title->getLocalURL( $urlaction );
01127     }
01128 
01134     static function makeI18nUrl( $name, $urlaction = '' ) {
01135         $title = Title::newFromText( wfMessage( $name )->inContentLanguage()->text() );
01136         self::checkTitle( $title, $name );
01137         return $title->getLocalURL( $urlaction );
01138     }
01139 
01145     static function makeUrl( $name, $urlaction = '' ) {
01146         $title = Title::newFromText( $name );
01147         self::checkTitle( $title, $name );
01148 
01149         return $title->getLocalURL( $urlaction );
01150     }
01151 
01158     static function makeInternalOrExternalUrl( $name ) {
01159         if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $name ) ) {
01160             return $name;
01161         } else {
01162             return self::makeUrl( $name );
01163         }
01164     }
01165 
01173     static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
01174         $title = Title::makeTitleSafe( $namespace, $name );
01175         self::checkTitle( $title, $name );
01176 
01177         return $title->getLocalURL( $urlaction );
01178     }
01179 
01186     static function makeUrlDetails( $name, $urlaction = '' ) {
01187         $title = Title::newFromText( $name );
01188         self::checkTitle( $title, $name );
01189 
01190         return array(
01191             'href' => $title->getLocalURL( $urlaction ),
01192             'exists' => $title->getArticleID() != 0,
01193         );
01194     }
01195 
01202     static function makeKnownUrlDetails( $name, $urlaction = '' ) {
01203         $title = Title::newFromText( $name );
01204         self::checkTitle( $title, $name );
01205 
01206         return array(
01207             'href' => $title->getLocalURL( $urlaction ),
01208             'exists' => true
01209         );
01210     }
01211 
01218     static function checkTitle( &$title, $name ) {
01219         if ( !is_object( $title ) ) {
01220             $title = Title::newFromText( $name );
01221             if ( !is_object( $title ) ) {
01222                 $title = Title::newFromText( '--error: link target missing--' );
01223             }
01224         }
01225     }
01226 
01248     function buildSidebar() {
01249         global $wgMemc, $wgEnableSidebarCache, $wgSidebarCacheExpiry;
01250         wfProfileIn( __METHOD__ );
01251 
01252         $key = wfMemcKey( 'sidebar', $this->getLanguage()->getCode() );
01253 
01254         if ( $wgEnableSidebarCache ) {
01255             $cachedsidebar = $wgMemc->get( $key );
01256             if ( $cachedsidebar ) {
01257                 wfProfileOut( __METHOD__ );
01258                 return $cachedsidebar;
01259             }
01260         }
01261 
01262         $bar = array();
01263         $this->addToSidebar( $bar, 'sidebar' );
01264 
01265         wfRunHooks( 'SkinBuildSidebar', array( $this, &$bar ) );
01266         if ( $wgEnableSidebarCache ) {
01267             $wgMemc->set( $key, $bar, $wgSidebarCacheExpiry );
01268         }
01269 
01270         wfProfileOut( __METHOD__ );
01271         return $bar;
01272     }
01273 
01283     function addToSidebar( &$bar, $message ) {
01284         $this->addToSidebarPlain( $bar, wfMessage( $message )->inContentLanguage()->plain() );
01285     }
01286 
01294     function addToSidebarPlain( &$bar, $text ) {
01295         $lines = explode( "\n", $text );
01296 
01297         $heading = '';
01298 
01299         foreach ( $lines as $line ) {
01300             if ( strpos( $line, '*' ) !== 0 ) {
01301                 continue;
01302             }
01303             $line = rtrim( $line, "\r" ); // for Windows compat
01304 
01305             if ( strpos( $line, '**' ) !== 0 ) {
01306                 $heading = trim( $line, '* ' );
01307                 if ( !array_key_exists( $heading, $bar ) ) {
01308                     $bar[$heading] = array();
01309                 }
01310             } else {
01311                 $line = trim( $line, '* ' );
01312 
01313                 if ( strpos( $line, '|' ) !== false ) { // sanity check
01314                     $line = MessageCache::singleton()->transform( $line, false, null, $this->getTitle() );
01315                     $line = array_map( 'trim', explode( '|', $line, 2 ) );
01316                     if ( count( $line ) !== 2 ) {
01317                         // Second sanity check, could be hit by people doing
01318                         // funky stuff with parserfuncs... (bug 33321)
01319                         continue;
01320                     }
01321 
01322                     $extraAttribs = array();
01323 
01324                     $msgLink = $this->msg( $line[0] )->inContentLanguage();
01325                     if ( $msgLink->exists() ) {
01326                         $link = $msgLink->text();
01327                         if ( $link == '-' ) {
01328                             continue;
01329                         }
01330                     } else {
01331                         $link = $line[0];
01332                     }
01333                     $msgText = $this->msg( $line[1] );
01334                     if ( $msgText->exists() ) {
01335                         $text = $msgText->text();
01336                     } else {
01337                         $text = $line[1];
01338                     }
01339 
01340                     if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $link ) ) {
01341                         $href = $link;
01342 
01343                         // Parser::getExternalLinkAttribs won't work here because of the Namespace things
01344                         global $wgNoFollowLinks, $wgNoFollowDomainExceptions;
01345                         if ( $wgNoFollowLinks && !wfMatchesDomainList( $href, $wgNoFollowDomainExceptions ) ) {
01346                             $extraAttribs['rel'] = 'nofollow';
01347                         }
01348 
01349                         global $wgExternalLinkTarget;
01350                         if ( $wgExternalLinkTarget ) {
01351                             $extraAttribs['target'] = $wgExternalLinkTarget;
01352                         }
01353                     } else {
01354                         $title = Title::newFromText( $link );
01355 
01356                         if ( $title ) {
01357                             $title = $title->fixSpecialName();
01358                             $href = $title->getLinkURL();
01359                         } else {
01360                             $href = 'INVALID-TITLE';
01361                         }
01362                     }
01363 
01364                     $bar[$heading][] = array_merge( array(
01365                         'text' => $text,
01366                         'href' => $href,
01367                         'id' => 'n-' . Sanitizer::escapeId( strtr( $line[1], ' ', '-' ), 'noninitial' ),
01368                         'active' => false
01369                     ), $extraAttribs );
01370                 } else {
01371                     continue;
01372                 }
01373             }
01374         }
01375 
01376         return $bar;
01377     }
01378 
01390     public function commonPrintStylesheet() {
01391         wfDeprecated( __METHOD__, '1.22' );
01392         return false;
01393     }
01394 
01400     function getNewtalks() {
01401 
01402         $newMessagesAlert = '';
01403         $user = $this->getUser();
01404         $newtalks = $user->getNewMessageLinks();
01405         $out = $this->getOutput();
01406 
01407         // Allow extensions to disable or modify the new messages alert
01408         if ( !wfRunHooks( 'GetNewMessagesAlert', array( &$newMessagesAlert, $newtalks, $user, $out ) ) ) {
01409             return '';
01410         }
01411         if ( $newMessagesAlert ) {
01412             return $newMessagesAlert;
01413         }
01414 
01415         if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
01416             $uTalkTitle = $user->getTalkPage();
01417             $lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
01418             $nofAuthors = 0;
01419             if ( $lastSeenRev !== null ) {
01420                 $plural = true; // Default if we have a last seen revision: if unknown, use plural
01421                 $latestRev = Revision::newFromTitle( $uTalkTitle, false, Revision::READ_NORMAL );
01422                 if ( $latestRev !== null ) {
01423                     // Singular if only 1 unseen revision, plural if several unseen revisions.
01424                     $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
01425                     $nofAuthors = $uTalkTitle->countAuthorsBetween(
01426                         $lastSeenRev, $latestRev, 10, 'include_new' );
01427                 }
01428             } else {
01429                 // Singular if no revision -> diff link will show latest change only in any case
01430                 $plural = false;
01431             }
01432             $plural = $plural ? 999 : 1;
01433             // 999 signifies "more than one revision". We don't know how many, and even if we did,
01434             // the number of revisions or authors is not necessarily the same as the number of
01435             // "messages".
01436             $newMessagesLink = Linker::linkKnown(
01437                 $uTalkTitle,
01438                 $this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
01439                 array(),
01440                 array( 'redirect' => 'no' )
01441             );
01442 
01443             $newMessagesDiffLink = Linker::linkKnown(
01444                 $uTalkTitle,
01445                 $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(),
01446                 array(),
01447                 $lastSeenRev !== null
01448                     ? array( 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' )
01449                     : array( 'diff' => 'cur' )
01450             );
01451 
01452             if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
01453                 $newMessagesAlert = $this->msg(
01454                     'youhavenewmessagesfromusers',
01455                     $newMessagesLink,
01456                     $newMessagesDiffLink
01457                 )->numParams( $nofAuthors, $plural );
01458             } else {
01459                 // $nofAuthors === 11 signifies "11 or more" ("more than 10")
01460                 $newMessagesAlert = $this->msg(
01461                     $nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
01462                     $newMessagesLink,
01463                     $newMessagesDiffLink
01464                 )->numParams( $plural );
01465             }
01466             $newMessagesAlert = $newMessagesAlert->text();
01467             # Disable Squid cache
01468             $out->setSquidMaxage( 0 );
01469         } elseif ( count( $newtalks ) ) {
01470             $sep = $this->msg( 'newtalkseparator' )->escaped();
01471             $msgs = array();
01472 
01473             foreach ( $newtalks as $newtalk ) {
01474                 $msgs[] = Xml::element(
01475                     'a',
01476                     array( 'href' => $newtalk['link'] ), $newtalk['wiki']
01477                 );
01478             }
01479             $parts = implode( $sep, $msgs );
01480             $newMessagesAlert = $this->msg( 'youhavenewmessagesmulti' )->rawParams( $parts )->escaped();
01481             $out->setSquidMaxage( 0 );
01482         }
01483 
01484         return $newMessagesAlert;
01485     }
01486 
01493     private function getCachedNotice( $name ) {
01494         global $wgRenderHashAppend, $parserMemc, $wgContLang;
01495 
01496         wfProfileIn( __METHOD__ );
01497 
01498         $needParse = false;
01499 
01500         if ( $name === 'default' ) {
01501             // special case
01502             global $wgSiteNotice;
01503             $notice = $wgSiteNotice;
01504             if ( empty( $notice ) ) {
01505                 wfProfileOut( __METHOD__ );
01506                 return false;
01507             }
01508         } else {
01509             $msg = $this->msg( $name )->inContentLanguage();
01510             if ( $msg->isDisabled() ) {
01511                 wfProfileOut( __METHOD__ );
01512                 return false;
01513             }
01514             $notice = $msg->plain();
01515         }
01516 
01517         // Use the extra hash appender to let eg SSL variants separately cache.
01518         $key = wfMemcKey( $name . $wgRenderHashAppend );
01519         $cachedNotice = $parserMemc->get( $key );
01520         if ( is_array( $cachedNotice ) ) {
01521             if ( md5( $notice ) == $cachedNotice['hash'] ) {
01522                 $notice = $cachedNotice['html'];
01523             } else {
01524                 $needParse = true;
01525             }
01526         } else {
01527             $needParse = true;
01528         }
01529 
01530         if ( $needParse ) {
01531             $parsed = $this->getOutput()->parse( $notice );
01532             $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
01533             $notice = $parsed;
01534         }
01535 
01536         $notice = Html::rawElement( 'div', array( 'id' => 'localNotice',
01537             'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ), $notice );
01538         wfProfileOut( __METHOD__ );
01539         return $notice;
01540     }
01541 
01547     function getNamespaceNotice() {
01548         wfProfileIn( __METHOD__ );
01549 
01550         $key = 'namespacenotice-' . $this->getTitle()->getNsText();
01551         $namespaceNotice = $this->getCachedNotice( $key );
01552         if ( $namespaceNotice && substr( $namespaceNotice, 0, 7 ) != '<p>&lt;' ) {
01553             $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . '</div>';
01554         } else {
01555             $namespaceNotice = '';
01556         }
01557 
01558         wfProfileOut( __METHOD__ );
01559         return $namespaceNotice;
01560     }
01561 
01567     function getSiteNotice() {
01568         wfProfileIn( __METHOD__ );
01569         $siteNotice = '';
01570 
01571         if ( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice, $this ) ) ) {
01572             if ( is_object( $this->getUser() ) && $this->getUser()->isLoggedIn() ) {
01573                 $siteNotice = $this->getCachedNotice( 'sitenotice' );
01574             } else {
01575                 $anonNotice = $this->getCachedNotice( 'anonnotice' );
01576                 if ( !$anonNotice ) {
01577                     $siteNotice = $this->getCachedNotice( 'sitenotice' );
01578                 } else {
01579                     $siteNotice = $anonNotice;
01580                 }
01581             }
01582             if ( !$siteNotice ) {
01583                 $siteNotice = $this->getCachedNotice( 'default' );
01584             }
01585         }
01586 
01587         wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice, $this ) );
01588         wfProfileOut( __METHOD__ );
01589         return $siteNotice;
01590     }
01591 
01605     public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
01606         // HTML generated here should probably have userlangattributes
01607         // added to it for LTR text on RTL pages
01608 
01609         $lang = wfGetLangObj( $lang );
01610 
01611         $attribs = array();
01612         if ( !is_null( $tooltip ) ) {
01613             # Bug 25462: undo double-escaping.
01614             $tooltip = Sanitizer::decodeCharReferences( $tooltip );
01615             $attribs['title'] = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
01616                 ->inLanguage( $lang )->text();
01617         }
01618         $link = Linker::link( $nt, wfMessage( 'editsection' )->inLanguage( $lang )->text(),
01619             $attribs,
01620             array( 'action' => 'edit', 'section' => $section ),
01621             array( 'noclasses', 'known' )
01622         );
01623 
01624         # Add the brackets and the span and run the hook.
01625         $result = '<span class="mw-editsection">'
01626             . '<span class="mw-editsection-bracket">[</span>'
01627             . $link
01628             . '<span class="mw-editsection-bracket">]</span>'
01629             . '</span>';
01630 
01631         wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
01632         return $result;
01633     }
01634 
01644     function __call( $fname, $args ) {
01645         $realFunction = array( 'Linker', $fname );
01646         if ( is_callable( $realFunction ) ) {
01647             wfDeprecated( get_class( $this ) . '::' . $fname, '1.21' );
01648             return call_user_func_array( $realFunction, $args );
01649         } else {
01650             $className = get_class( $this );
01651             throw new MWException( "Call to undefined method $className::$fname" );
01652         }
01653     }
01654 
01655 }