read())) { // Skip non-PHP files, hidden files, and '.dep' includes $matches = array(); if(preg_match('/^([^.]*)\.php$/',$file, $matches)) { $aSkin = $matches[1]; $wgValidSkinNames[strtolower($aSkin)] = $aSkin; } } $skinDir->close(); $skinsInitialised = true; wfProfileOut( __METHOD__ . '-init' ); } return $wgValidSkinNames; } /** * Fetch the list of usable skins in regards to $wgSkipSkins. * Useful for Special:Preferences and other places where you * only want to show skins users _can_ use. * @return array of strings */ public static function getUsableSkins() { global $wgSkipSkins; $usableSkins = self::getSkinNames(); foreach ( $wgSkipSkins as $skip ) { unset( $usableSkins[$skip] ); } return $usableSkins; } /** * Normalize a skin preference value to a form that can be loaded. * If a skin can't be found, it will fall back to the configured * default (or the old 'Classic' skin if that's broken). * @param string $key * @return string * @static */ static function normalizeKey( $key ) { global $wgDefaultSkin; $skinNames = Skin::getSkinNames(); if( $key == '' ) { // Don't return the default immediately; // in a misconfiguration we need to fall back. $key = $wgDefaultSkin; } if( isset( $skinNames[$key] ) ) { return $key; } // Older versions of the software used a numeric setting // in the user preferences. $fallback = array( 0 => $wgDefaultSkin, 1 => 'nostalgia', 2 => 'cologneblue' ); if( isset( $fallback[$key] ) ){ $key = $fallback[$key]; } if( isset( $skinNames[$key] ) ) { return $key; } else { return 'monobook'; } } /** * Factory method for loading a skin of a given type * @param string $key 'monobook', 'standard', etc * @return Skin * @static */ static function &newFromKey( $key ) { global $wgStyleDirectory; $key = Skin::normalizeKey( $key ); $skinNames = Skin::getSkinNames(); $skinName = $skinNames[$key]; $className = 'Skin'.ucfirst($key); # Grab the skin class and initialise it. if ( !class_exists( $className ) ) { // Preload base classes to work around APC/PHP5 bug $deps = "{$wgStyleDirectory}/{$skinName}.deps.php"; if( file_exists( $deps ) ) include_once( $deps ); require_once( "{$wgStyleDirectory}/{$skinName}.php" ); # Check if we got if not failback to default skin if( !class_exists( $className ) ) { # DO NOT die if the class isn't found. This breaks maintenance # scripts and can cause a user account to be unrecoverable # except by SQL manipulation if a previously valid skin name # is no longer valid. wfDebug( "Skin class does not exist: $className\n" ); $className = 'SkinMonobook'; require_once( "{$wgStyleDirectory}/MonoBook.php" ); } } $skin = new $className; return $skin; } /** @return string path to the skin stylesheet */ function getStylesheet() { return 'common/wikistandard.css'; } /** @return string skin name */ public function getSkinName() { return $this->skinname; } function qbSetting() { global $wgOut, $wgUser; if ( $wgOut->isQuickbarSuppressed() ) { return 0; } $q = $wgUser->getOption( 'quickbar', 0 ); return $q; } function initPage( OutputPage $out ) { global $wgFavicon, $wgAppleTouchIcon; wfProfileIn( __METHOD__ ); # Generally the order of the favicon and apple-touch-icon links # should not matter, but Konqueror (3.5.9 at least) incorrectly # uses whichever one appears later in the HTML source. Make sure # apple-touch-icon is specified first to avoid this. if( false !== $wgAppleTouchIcon ) { $out->addLink( array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) ); } if( false !== $wgFavicon ) { $out->addLink( array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) ); } # OpenSearch description link $out->addLink( array( 'rel' => 'search', 'type' => 'application/opensearchdescription+xml', 'href' => wfScript( 'opensearch_desc' ), 'title' => wfMsgForContent( 'opensearch-desc' ), )); $this->addMetadataLinks($out); $this->mRevisionId = $out->mRevisionId; $this->preloadExistence(); wfProfileOut( __METHOD__ ); } /** * Preload the existence of three commonly-requested pages in a single query */ function preloadExistence() { global $wgUser, $wgTitle; // User/talk link $titles = array( $wgUser->getUserPage(), $wgUser->getTalkPage() ); // Other tab link if ( $wgTitle->getNamespace() == NS_SPECIAL ) { // nothing } elseif ( $wgTitle->isTalkPage() ) { $titles[] = $wgTitle->getSubjectPage(); } else { $titles[] = $wgTitle->getTalkPage(); } $lb = new LinkBatch( $titles ); $lb->execute(); } function addMetadataLinks( OutputPage $out ) { global $wgTitle, $wgEnableDublinCoreRdf, $wgEnableCreativeCommonsRdf; global $wgRightsPage, $wgRightsUrl; if( $out->isArticleRelated() ) { # note: buggy CC software only reads first "meta" link if( $wgEnableCreativeCommonsRdf ) { $out->addMetadataLink( array( 'title' => 'Creative Commons', 'type' => 'application/rdf+xml', 'href' => $wgTitle->getLocalURL( 'action=creativecommons') ) ); } if( $wgEnableDublinCoreRdf ) { $out->addMetadataLink( array( 'title' => 'Dublin Core', 'type' => 'application/rdf+xml', 'href' => $wgTitle->getLocalURL( 'action=dublincore' ) ) ); } } $copyright = ''; if( $wgRightsPage ) { $copy = Title::newFromText( $wgRightsPage ); if( $copy ) { $copyright = $copy->getLocalURL(); } } if( !$copyright && $wgRightsUrl ) { $copyright = $wgRightsUrl; } if( $copyright ) { $out->addLink( array( 'rel' => 'copyright', 'href' => $copyright ) ); } } function setMembers(){ global $wgTitle, $wgUser; $this->mTitle = $wgTitle; $this->mUser = $wgUser; $this->userpage = $wgUser->getUserPage()->getPrefixedText(); $this->usercss = false; } function outputPage( OutputPage $out ) { global $wgDebugComments; wfProfileIn( __METHOD__ ); $this->setMembers(); $this->initPage( $out ); // See self::afterContentHook() for documentation $afterContent = $this->afterContentHook(); $out->out( $out->headElement( $this ) ); $out->out( "\ngetBodyOptions(); foreach ( $ops as $name => $val ) { $out->out( " $name='$val'" ); } $out->out( ">\n" ); if ( $wgDebugComments ) { $out->out( "\n" ); } $out->out( $this->beforeContent() ); $out->out( $out->mBodytext . "\n" ); $out->out( $this->afterContent() ); $out->out( $afterContent ); $out->out( $this->bottomScripts() ); $out->out( wfReportTime() ); $out->out( "\n" ); wfProfileOut( __METHOD__ ); } static function makeVariablesScript( $data ) { global $wgJsMimeType; $r = array( "\n"; return implode( "\n\t\t", $r ); } /** * Make a " ); global $wgUseSiteJs; if ($wgUseSiteJs) { $jsCache = $wgUser->isLoggedIn() ? '&smaxage=0' : ''; $r[] = ""; } if( $allowUserJs && $wgUser->isLoggedIn() ) { $userpage = $wgUser->getUserPage(); $userjs = htmlspecialchars( self::makeUrl( $userpage->getPrefixedText().'/'.$this->getSkinName().'.js', 'action=raw&ctype='.$wgJsMimeType)); $r[] = '"; } return $vars . "\t\t" . implode ( "\n\t\t", $r ); } /** * To make it harder for someone to slip a user a fake * user-JavaScript or user-CSS preview, a random token * is associated with the login session. If it's not * passed back with the preview request, we won't render * the code. * * @param string $action * @return bool * @private */ function userCanPreview( $action ) { global $wgTitle, $wgRequest, $wgUser; if( $action != 'submit' ) return false; if( !$wgRequest->wasPosted() ) return false; if( !$wgTitle->userCanEditCssJsSubpage() ) return false; return $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ); } /** * generated JavaScript action=raw&gen=js * This returns MediaWiki:Common.js and MediaWiki:[Skinname].js concate- * nated together. For some bizarre reason, it does *not* return any * custom user JS from subpages. Huh? * * There's absolutely no reason to have separate Monobook/Common JSes. * Any JS that cares can just check the skin variable generated at the * top. For now Monobook.js will be maintained, but it should be consi- * dered deprecated. * * @return string */ public function generateUserJs() { global $wgStylePath; wfProfileIn( __METHOD__ ); $s = "/* generated javascript */\n"; $s .= "var skin = '" . Xml::escapeJsString( $this->getSkinName() ) . "';\n"; $s .= "var stylepath = '" . Xml::escapeJsString( $wgStylePath ) . "';"; $s .= "\n\n/* MediaWiki:Common.js */\n"; $commonJs = wfMsgForContent('common.js'); if ( !wfEmptyMsg ( 'common.js', $commonJs ) ) { $s .= $commonJs; } $s .= "\n\n/* MediaWiki:".ucfirst( $this->getSkinName() ).".js */\n"; // avoid inclusion of non defined user JavaScript (with custom skins only) // by checking for default message content $msgKey = ucfirst( $this->getSkinName() ).'.js'; $userJS = wfMsgForContent($msgKey); if ( !wfEmptyMsg( $msgKey, $userJS ) ) { $s .= $userJS; } wfProfileOut( __METHOD__ ); return $s; } /** * generate user stylesheet for action=raw&gen=css */ public function generateUserStylesheet() { wfProfileIn( __METHOD__ ); $s = "/* generated user stylesheet */\n" . $this->reallyGenerateUserStylesheet(); wfProfileOut( __METHOD__ ); return $s; } /** * Split for easier subclassing in SkinSimple, SkinStandard and SkinCologneBlue */ protected function reallyGenerateUserStylesheet(){ global $wgUser; $s = ''; if (($undopt = $wgUser->getOption("underline")) < 2) { $underline = $undopt ? 'underline' : 'none'; $s .= "a { text-decoration: $underline; }\n"; } if( $wgUser->getOption( 'highlightbroken' ) ) { $s .= "a.new, #quickbar a.new { color: #CC2200; }\n"; } else { $s .= <<getOption( 'justify' ) ) { $s .= "#article, #bodyContent, #mw_content { text-align: justify; }\n"; } if( !$wgUser->getOption( 'showtoc' ) ) { $s .= "#toc { display: none; }\n"; } if( !$wgUser->getOption( 'editsection' ) ) { $s .= ".editsection { display: none; }\n"; } return $s; } /** * @private */ function setupUserCss( OutputPage $out ) { global $wgRequest, $wgContLang, $wgUser; global $wgAllowUserCss, $wgUseSiteCss, $wgSquidMaxage, $wgStylePath; wfProfileIn( __METHOD__ ); $this->setupSkinUserCss( $out ); $siteargs = array( 'action' => 'raw', 'maxage' => $wgSquidMaxage, ); // Add any extension CSS foreach( $out->getExtStyle() as $tag ) { $out->addStyle( $tag['href'] ); } // If we use the site's dynamic CSS, throw that in, too // Per-site custom styles if( $wgUseSiteCss ) { global $wgHandheldStyle; $query = wfArrayToCGI( array( 'usemsgcache' => 'yes', 'ctype' => 'text/css', 'smaxage' => $wgSquidMaxage ) + $siteargs ); # Site settings must override extension css! (bug 15025) $out->addStyle( self::makeNSUrl( 'Common.css', $query, NS_MEDIAWIKI ) ); $out->addStyle( self::makeNSUrl( 'Print.css', $query, NS_MEDIAWIKI ), 'print' ); if( $wgHandheldStyle ) { $out->addStyle( self::makeNSUrl( 'Handheld.css', $query, NS_MEDIAWIKI ), 'handheld' ); } $out->addStyle( self::makeNSUrl( $this->getSkinName() . '.css', $query, NS_MEDIAWIKI ) ); } if( $wgUser->isLoggedIn() ) { // Ensure that logged-in users' generated CSS isn't clobbered // by anons' publicly cacheable generated CSS. $siteargs['smaxage'] = '0'; $siteargs['ts'] = $wgUser->mTouched; } // Per-user styles based on preferences $siteargs['gen'] = 'css'; if( ( $us = $wgRequest->getVal( 'useskin', '' ) ) !== '' ) { $siteargs['useskin'] = $us; } $out->addStyle( self::makeUrl( '-', wfArrayToCGI( $siteargs ) ) ); // Per-user custom style pages if( $wgAllowUserCss && $wgUser->isLoggedIn() ) { $action = $wgRequest->getVal('action'); # If we're previewing the CSS page, use it if( $this->mTitle->isCssSubpage() && $this->userCanPreview( $action ) ) { $previewCss = $wgRequest->getText('wpTextbox1'); // @FIXME: properly escape the cdata! $this->usercss = "/**/"; } else { $out->addStyle( self::makeUrl($this->userpage . '/' . $this->getSkinName() .'.css', 'action=raw&ctype=text/css' ) ); } } wfProfileOut( __METHOD__ ); } /** * Add skin specific stylesheets * @param $out OutputPage */ function setupSkinUserCss( OutputPage $out ) { $out->addStyle( 'common/shared.css' ); $out->addStyle( 'common/oldshared.css' ); $out->addStyle( $this->getStylesheet() ); $out->addStyle( 'common/common_rtl.css', '', '', 'rtl' ); } function getBodyOptions() { global $wgUser, $wgTitle, $wgOut, $wgRequest, $wgContLang; extract( $wgRequest->getValues( 'oldid', 'redirect', 'diff' ) ); if ( 0 != $wgTitle->getNamespace() ) { $a = array( 'bgcolor' => '#ffffec' ); } else $a = array( 'bgcolor' => '#FFFFFF' ); if($wgOut->isArticle() && $wgUser->getOption('editondblclick') && $wgTitle->quickUserCan( 'edit' ) ) { $s = $wgTitle->getFullURL( $this->editUrlOptions() ); $s = 'document.location = "' .Xml::escapeJsString( $s ) .'";'; $a += array ('ondblclick' => $s); } $a['onload'] = $wgOut->getOnloadHandler(); $a['class'] = 'mediawiki' . ' '.( $wgContLang->isRTL() ? "rtl" : "ltr" ). ' '.$this->getPageClasses( $wgTitle ) . ' skin-'. Sanitizer::escapeClass( $this->getSkinName( ) ); return $a; } function getPageClasses( $title ) { $numeric = 'ns-'.$title->getNamespace(); if( $title->getNamespace() == NS_SPECIAL ) { $type = "ns-special"; } elseif( $title->isTalkPage() ) { $type = "ns-talk"; } else { $type = "ns-subject"; } $name = Sanitizer::escapeClass( 'page-'.$title->getPrefixedText() ); return "$numeric $type $name"; } /** * URL to the logo */ function getLogo() { global $wgLogo; return $wgLogo; } /** * This will be called immediately after the tag. Split into * two functions to make it easier to subclass. */ function beforeContent() { return $this->doBeforeContent(); } function doBeforeContent() { global $wgContLang; $fname = 'Skin::doBeforeContent'; wfProfileIn( $fname ); $s = ''; $qb = $this->qbSetting(); if( $langlinks = $this->otherLanguages() ) { $rows = 2; $borderhack = ''; } else { $rows = 1; $langlinks = false; $borderhack = 'class="top"'; } $s .= "\n
\n
\n" . "\n\n"; $shove = ( $qb != 0 ); $left = ( $qb == 1 || $qb == 3 ); if( $wgContLang->isRTL() ) $left = !$left; if( !$shove ) { $s .= "'; } elseif( $left ) { $s .= $this->getQuickbarCompensator( $rows ); } $l = $wgContLang->isRTL() ? 'right' : 'left'; $s .= "\n"; if ( $langlinks ) { $s .= "\n\n\n"; } if ( $shove && !$left ) { # Right $s .= $this->getQuickbarCompensator( $rows ); } $s .= "\n
\n" . $this->logoText() . '\n"; $s .= $this->topLinks() ; $s .= "

" . $this->pageTitleLinks() . "

\n"; $r = $wgContLang->isRTL() ? "left" : "right"; $s .= "
"; $s .= $this->nameAndLogin(); $s .= "\n
" . $this->searchForm() . "
$langlinks
\n
\n"; $s .= "\n
\n"; $notice = wfGetSiteNotice(); if( $notice ) { $s .= "\n
$notice
\n"; } $s .= $this->pageTitle(); $s .= $this->pageSubtitle() ; $s .= $this->getCategories(); wfProfileOut( $fname ); return $s; } function getCategoryLinks() { global $wgOut, $wgTitle, $wgUseCategoryBrowser; global $wgContLang, $wgUser; if( count( $wgOut->mCategoryLinks ) == 0 ) return ''; # Separator $sep = wfMsgHtml( 'catseparator' ); // Use Unicode bidi embedding override characters, // to make sure links don't smash each other up in ugly ways. $dir = $wgContLang->isRTL() ? 'rtl' : 'ltr'; $embed = ""; $pop = ''; $allCats = $wgOut->getCategoryLinks(); $s = ''; $colon = wfMsgExt( 'colon-separator', 'escapenoentities' ); if ( !empty( $allCats['normal'] ) ) { $t = $embed . implode ( "{$pop} {$sep} {$embed}" , $allCats['normal'] ) . $pop; $msg = wfMsgExt( 'pagecategories', array( 'parsemag', 'escapenoentities' ), count( $allCats['normal'] ) ); $s .= ''; } # Hidden categories if ( isset( $allCats['hidden'] ) ) { if ( $wgUser->getBoolOption( 'showhiddencats' ) ) { $class ='mw-hidden-cats-user-shown'; } elseif ( $wgTitle->getNamespace() == NS_CATEGORY ) { $class = 'mw-hidden-cats-ns-shown'; } else { $class = 'mw-hidden-cats-hidden'; } $s .= "
" . wfMsgExt( 'hidden-categories', array( 'parsemag', 'escapenoentities' ), count( $allCats['hidden'] ) ) . $colon . $embed . implode( "$pop $sep $embed", $allCats['hidden'] ) . $pop . "
"; } # optional 'dmoz-like' category browser. Will be shown under the list # of categories an article belong to if( $wgUseCategoryBrowser ){ $s .= '

'; # get a big array of the parents tree $parenttree = $wgTitle->getParentCategoryTree(); # Skin object passed by reference cause it can not be # accessed under the method subfunction drawCategoryBrowser $tempout = explode("\n", Skin::drawCategoryBrowser($parenttree, $this) ); # Clean out bogus first entry and sort them unset($tempout[0]); asort($tempout); # Output one per line $s .= implode("
\n", $tempout); } return $s; } /** Render the array as a serie of links. * @param $tree Array: categories tree returned by Title::getParentCategoryTree * @param &skin Object: skin passed by reference * @return String separated by >, terminate with "\n" */ function drawCategoryBrowser( $tree, &$skin ){ $return = ''; foreach ($tree as $element => $parent) { if (empty($parent)) { # element start a new list $return .= "\n"; } else { # grab the others elements $return .= Skin::drawCategoryBrowser($parent, $skin) . ' > '; } # add our current element to the list $eltitle = Title::newFromText($element); $return .= $skin->link( $eltitle, $eltitle->getText() ) ; } return $return; } function getCategories() { $catlinks=$this->getCategoryLinks(); $classes = 'catlinks'; if( strpos( $catlinks, '