MediaWiki
REL1_23
|
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 ) . ' > '; 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 .= '< '; 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><' ) { 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 }