MediaWiki
REL1_19
|
00001 <?php 00002 if ( !defined( 'MEDIAWIKI' ) ) { 00003 die( 1 ); 00004 } 00005 00021 class OutputPage extends ContextSource { 00023 var $mMetatags = array(); 00024 00026 var $mKeywords = array(); 00027 00028 var $mLinktags = array(); 00029 00031 var $mExtStyles = array(); 00032 00034 var $mPagetitle = ''; 00035 00037 var $mBodytext = ''; 00038 00044 public $mDebugtext = ''; // TODO: we might want to replace it by wfDebug() wfDebugLog() 00045 00047 var $mHTMLtitle = ''; 00048 00050 var $mIsarticle = false; 00051 00056 var $mIsArticleRelated = true; 00057 00062 var $mPrintable = false; 00063 00070 private $mSubtitle = array(); 00071 00072 var $mRedirect = ''; 00073 var $mStatusCode; 00074 00079 var $mLastModified = ''; 00080 00091 var $mETag = false; 00092 00093 var $mCategoryLinks = array(); 00094 var $mCategories = array(); 00095 00097 var $mLanguageLinks = array(); 00098 00105 var $mScripts = ''; 00106 00110 var $mInlineStyles = ''; 00111 00112 // 00113 var $mLinkColours; 00114 00119 var $mPageLinkTitle = ''; 00120 00122 var $mHeadItems = array(); 00123 00124 // @todo FIXME: Next variables probably comes from the resource loader 00125 var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array(); 00126 var $mResourceLoader; 00127 var $mJsConfigVars = array(); 00128 00130 var $mInlineMsg = array(); 00131 00132 var $mTemplateIds = array(); 00133 var $mImageTimeKeys = array(); 00134 00135 var $mRedirectCode = ''; 00136 00137 var $mFeedLinksAppendQuery = null; 00138 00144 protected $mAllowedModuleOrigin = ResourceLoaderModule::ORIGIN_ALL; 00145 00150 var $mDoNothing = false; 00151 00152 // Parser related. 00153 var $mContainsOldMagic = 0, $mContainsNewMagic = 0; 00154 00159 protected $mParserOptions = null; 00160 00167 var $mFeedLinks = array(); 00168 00169 // Gwicke work on squid caching? Roughly from 2003. 00170 var $mEnableClientCache = true; 00171 00176 var $mArticleBodyOnly = false; 00177 00178 var $mNewSectionLink = false; 00179 var $mHideNewSectionLink = false; 00180 00186 var $mNoGallery = false; 00187 00188 // should be private. 00189 var $mPageTitleActionText = ''; 00190 var $mParseWarnings = array(); 00191 00192 // Cache stuff. Looks like mEnableClientCache 00193 var $mSquidMaxage = 0; 00194 00195 // @todo document 00196 var $mPreventClickjacking = true; 00197 00199 var $mRevisionId = null; 00200 private $mRevisionTimestamp = null; 00201 00202 var $mFileVersion = null; 00203 00212 var $styles = array(); 00213 00217 protected $mJQueryDone = false; 00218 00219 private $mIndexPolicy = 'index'; 00220 private $mFollowPolicy = 'follow'; 00221 private $mVaryHeader = array( 00222 'Accept-Encoding' => array( 'list-contains=gzip' ), 00223 'Cookie' => null 00224 ); 00225 00232 private $mRedirectedFrom = null; 00233 00239 function __construct( IContextSource $context = null ) { 00240 if ( $context === null ) { 00241 # Extensions should use `new RequestContext` instead of `new OutputPage` now. 00242 wfDeprecated( __METHOD__ ); 00243 } else { 00244 $this->setContext( $context ); 00245 } 00246 } 00247 00254 public function redirect( $url, $responsecode = '302' ) { 00255 # Strip newlines as a paranoia check for header injection in PHP<5.1.2 00256 $this->mRedirect = str_replace( "\n", '', $url ); 00257 $this->mRedirectCode = $responsecode; 00258 } 00259 00265 public function getRedirect() { 00266 return $this->mRedirect; 00267 } 00268 00274 public function setStatusCode( $statusCode ) { 00275 $this->mStatusCode = $statusCode; 00276 } 00277 00285 function addMeta( $name, $val ) { 00286 array_push( $this->mMetatags, array( $name, $val ) ); 00287 } 00288 00294 function addKeyword( $text ) { 00295 if( is_array( $text ) ) { 00296 $this->mKeywords = array_merge( $this->mKeywords, $text ); 00297 } else { 00298 array_push( $this->mKeywords, $text ); 00299 } 00300 } 00301 00307 function addLink( $linkarr ) { 00308 array_push( $this->mLinktags, $linkarr ); 00309 } 00310 00318 function addMetadataLink( $linkarr ) { 00319 $linkarr['rel'] = $this->getMetadataAttribute(); 00320 $this->addLink( $linkarr ); 00321 } 00322 00328 public function getMetadataAttribute() { 00329 # note: buggy CC software only reads first "meta" link 00330 static $haveMeta = false; 00331 if ( $haveMeta ) { 00332 return 'alternate meta'; 00333 } else { 00334 $haveMeta = true; 00335 return 'meta'; 00336 } 00337 } 00338 00344 function addScript( $script ) { 00345 $this->mScripts .= $script . "\n"; 00346 } 00347 00356 public function addExtensionStyle( $url ) { 00357 array_push( $this->mExtStyles, $url ); 00358 } 00359 00365 function getExtStyle() { 00366 return $this->mExtStyles; 00367 } 00368 00376 public function addScriptFile( $file, $version = null ) { 00377 global $wgStylePath, $wgStyleVersion; 00378 // See if $file parameter is an absolute URL or begins with a slash 00379 if( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) { 00380 $path = $file; 00381 } else { 00382 $path = "{$wgStylePath}/common/{$file}"; 00383 } 00384 if ( is_null( $version ) ) 00385 $version = $wgStyleVersion; 00386 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) ); 00387 } 00388 00394 public function addInlineScript( $script ) { 00395 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n"; 00396 } 00397 00403 function getScript() { 00404 return $this->mScripts . $this->getHeadItems(); 00405 } 00406 00415 protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ){ 00416 $resourceLoader = $this->getResourceLoader(); 00417 $filteredModules = array(); 00418 foreach( $modules as $val ){ 00419 $module = $resourceLoader->getModule( $val ); 00420 if( $module instanceof ResourceLoaderModule 00421 && $module->getOrigin() <= $this->getAllowedModules( $type ) 00422 && ( is_null( $position ) || $module->getPosition() == $position ) ) 00423 { 00424 $filteredModules[] = $val; 00425 } 00426 } 00427 return $filteredModules; 00428 } 00429 00438 public function getModules( $filter = false, $position = null, $param = 'mModules' ) { 00439 $modules = array_values( array_unique( $this->$param ) ); 00440 return $filter 00441 ? $this->filterModules( $modules, $position ) 00442 : $modules; 00443 } 00444 00452 public function addModules( $modules ) { 00453 $this->mModules = array_merge( $this->mModules, (array)$modules ); 00454 } 00455 00464 public function getModuleScripts( $filter = false, $position = null ) { 00465 return $this->getModules( $filter, $position, 'mModuleScripts' ); 00466 } 00467 00475 public function addModuleScripts( $modules ) { 00476 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules ); 00477 } 00478 00487 public function getModuleStyles( $filter = false, $position = null ) { 00488 return $this->getModules( $filter, $position, 'mModuleStyles' ); 00489 } 00490 00498 public function addModuleStyles( $modules ) { 00499 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules ); 00500 } 00501 00510 public function getModuleMessages( $filter = false, $position = null ) { 00511 return $this->getModules( $filter, $position, 'mModuleMessages' ); 00512 } 00513 00521 public function addModuleMessages( $modules ) { 00522 $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules ); 00523 } 00524 00530 function getHeadItemsArray() { 00531 return $this->mHeadItems; 00532 } 00533 00539 function getHeadItems() { 00540 $s = ''; 00541 foreach ( $this->mHeadItems as $item ) { 00542 $s .= $item; 00543 } 00544 return $s; 00545 } 00546 00553 public function addHeadItem( $name, $value ) { 00554 $this->mHeadItems[$name] = $value; 00555 } 00556 00563 public function hasHeadItem( $name ) { 00564 return isset( $this->mHeadItems[$name] ); 00565 } 00566 00572 function setETag( $tag ) { 00573 $this->mETag = $tag; 00574 } 00575 00583 public function setArticleBodyOnly( $only ) { 00584 $this->mArticleBodyOnly = $only; 00585 } 00586 00592 public function getArticleBodyOnly() { 00593 return $this->mArticleBodyOnly; 00594 } 00595 00607 public function checkLastModified( $timestamp ) { 00608 global $wgCachePages, $wgCacheEpoch; 00609 00610 if ( !$timestamp || $timestamp == '19700101000000' ) { 00611 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" ); 00612 return false; 00613 } 00614 if( !$wgCachePages ) { 00615 wfDebug( __METHOD__ . ": CACHE DISABLED\n", false ); 00616 return false; 00617 } 00618 if( $this->getUser()->getOption( 'nocache' ) ) { 00619 wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false ); 00620 return false; 00621 } 00622 00623 $timestamp = wfTimestamp( TS_MW, $timestamp ); 00624 $modifiedTimes = array( 00625 'page' => $timestamp, 00626 'user' => $this->getUser()->getTouched(), 00627 'epoch' => $wgCacheEpoch 00628 ); 00629 wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) ); 00630 00631 $maxModified = max( $modifiedTimes ); 00632 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified ); 00633 00634 if( empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { 00635 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false ); 00636 return false; 00637 } 00638 00639 # Make debug info 00640 $info = ''; 00641 foreach ( $modifiedTimes as $name => $value ) { 00642 if ( $info !== '' ) { 00643 $info .= ', '; 00644 } 00645 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value ); 00646 } 00647 00648 # IE sends sizes after the date like this: 00649 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 00650 # this breaks strtotime(). 00651 $clientHeader = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] ); 00652 00653 wfSuppressWarnings(); // E_STRICT system time bitching 00654 $clientHeaderTime = strtotime( $clientHeader ); 00655 wfRestoreWarnings(); 00656 if ( !$clientHeaderTime ) { 00657 wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" ); 00658 return false; 00659 } 00660 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime ); 00661 00662 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " . 00663 wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false ); 00664 wfDebug( __METHOD__ . ": effective Last-Modified: " . 00665 wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false ); 00666 if( $clientHeaderTime < $maxModified ) { 00667 wfDebug( __METHOD__ . ": STALE, $info\n", false ); 00668 return false; 00669 } 00670 00671 # Not modified 00672 # Give a 304 response code and disable body output 00673 wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false ); 00674 ini_set( 'zlib.output_compression', 0 ); 00675 $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" ); 00676 $this->sendCacheControl(); 00677 $this->disable(); 00678 00679 // Don't output a compressed blob when using ob_gzhandler; 00680 // it's technically against HTTP spec and seems to confuse 00681 // Firefox when the response gets split over two packets. 00682 wfClearOutputBuffers(); 00683 00684 return true; 00685 } 00686 00693 public function setLastModified( $timestamp ) { 00694 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp ); 00695 } 00696 00705 public function setRobotPolicy( $policy ) { 00706 $policy = Article::formatRobotPolicy( $policy ); 00707 00708 if( isset( $policy['index'] ) ) { 00709 $this->setIndexPolicy( $policy['index'] ); 00710 } 00711 if( isset( $policy['follow'] ) ) { 00712 $this->setFollowPolicy( $policy['follow'] ); 00713 } 00714 } 00715 00723 public function setIndexPolicy( $policy ) { 00724 $policy = trim( $policy ); 00725 if( in_array( $policy, array( 'index', 'noindex' ) ) ) { 00726 $this->mIndexPolicy = $policy; 00727 } 00728 } 00729 00737 public function setFollowPolicy( $policy ) { 00738 $policy = trim( $policy ); 00739 if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) { 00740 $this->mFollowPolicy = $policy; 00741 } 00742 } 00743 00750 public function setPageTitleActionText( $text ) { 00751 $this->mPageTitleActionText = $text; 00752 } 00753 00759 public function getPageTitleActionText() { 00760 if ( isset( $this->mPageTitleActionText ) ) { 00761 return $this->mPageTitleActionText; 00762 } 00763 } 00764 00771 public function setHTMLTitle( $name ) { 00772 if ( $name instanceof Message ) { 00773 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text(); 00774 } else { 00775 $this->mHTMLtitle = $name; 00776 } 00777 } 00778 00784 public function getHTMLTitle() { 00785 return $this->mHTMLtitle; 00786 } 00787 00793 public function setRedirectedFrom( $t ) { 00794 $this->mRedirectedFrom = $t; 00795 } 00796 00805 public function setPageTitle( $name ) { 00806 if ( $name instanceof Message ) { 00807 $name = $name->setContext( $this->getContext() )->text(); 00808 } 00809 00810 # change "<script>foo&bar</script>" to "<script>foo&bar</script>" 00811 # but leave "<i>foobar</i>" alone 00812 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) ); 00813 $this->mPagetitle = $nameWithTags; 00814 00815 # change "<i>foo&bar</i>" to "foo&bar" 00816 $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) ); 00817 } 00818 00824 public function getPageTitle() { 00825 return $this->mPagetitle; 00826 } 00827 00833 public function setTitle( Title $t ) { 00834 $this->getContext()->setTitle( $t ); 00835 } 00836 00837 00843 public function setSubtitle( $str ) { 00844 $this->clearSubtitle(); 00845 $this->addSubtitle( $str ); 00846 } 00847 00854 public function appendSubtitle( $str ) { 00855 $this->addSubtitle( $str ); 00856 } 00857 00863 public function addSubtitle( $str ) { 00864 if ( $str instanceof Message ) { 00865 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse(); 00866 } else { 00867 $this->mSubtitle[] = $str; 00868 } 00869 } 00870 00876 public function addBacklinkSubtitle( Title $title ) { 00877 $query = array(); 00878 if ( $title->isRedirect() ) { 00879 $query['redirect'] = 'no'; 00880 } 00881 $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) ); 00882 } 00883 00887 public function clearSubtitle() { 00888 $this->mSubtitle = array(); 00889 } 00890 00896 public function getSubtitle() { 00897 return implode( "<br />\n\t\t\t\t", $this->mSubtitle ); 00898 } 00899 00904 public function setPrintable() { 00905 $this->mPrintable = true; 00906 } 00907 00913 public function isPrintable() { 00914 return $this->mPrintable; 00915 } 00916 00920 public function disable() { 00921 $this->mDoNothing = true; 00922 } 00923 00929 public function isDisabled() { 00930 return $this->mDoNothing; 00931 } 00932 00938 public function showNewSectionLink() { 00939 return $this->mNewSectionLink; 00940 } 00941 00947 public function forceHideNewSectionLink() { 00948 return $this->mHideNewSectionLink; 00949 } 00950 00959 public function setSyndicated( $show = true ) { 00960 if ( $show ) { 00961 $this->setFeedAppendQuery( false ); 00962 } else { 00963 $this->mFeedLinks = array(); 00964 } 00965 } 00966 00976 public function setFeedAppendQuery( $val ) { 00977 global $wgAdvertisedFeedTypes; 00978 00979 $this->mFeedLinks = array(); 00980 00981 foreach ( $wgAdvertisedFeedTypes as $type ) { 00982 $query = "feed=$type"; 00983 if ( is_string( $val ) ) { 00984 $query .= '&' . $val; 00985 } 00986 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query ); 00987 } 00988 } 00989 00996 public function addFeedLink( $format, $href ) { 00997 global $wgAdvertisedFeedTypes; 00998 00999 if ( in_array( $format, $wgAdvertisedFeedTypes ) ) { 01000 $this->mFeedLinks[$format] = $href; 01001 } 01002 } 01003 01008 public function isSyndicated() { 01009 return count( $this->mFeedLinks ) > 0; 01010 } 01011 01016 public function getSyndicationLinks() { 01017 return $this->mFeedLinks; 01018 } 01019 01025 public function getFeedAppendQuery() { 01026 return $this->mFeedLinksAppendQuery; 01027 } 01028 01036 public function setArticleFlag( $v ) { 01037 $this->mIsarticle = $v; 01038 if ( $v ) { 01039 $this->mIsArticleRelated = $v; 01040 } 01041 } 01042 01049 public function isArticle() { 01050 return $this->mIsarticle; 01051 } 01052 01059 public function setArticleRelated( $v ) { 01060 $this->mIsArticleRelated = $v; 01061 if ( !$v ) { 01062 $this->mIsarticle = false; 01063 } 01064 } 01065 01071 public function isArticleRelated() { 01072 return $this->mIsArticleRelated; 01073 } 01074 01081 public function addLanguageLinks( $newLinkArray ) { 01082 $this->mLanguageLinks += $newLinkArray; 01083 } 01084 01091 public function setLanguageLinks( $newLinkArray ) { 01092 $this->mLanguageLinks = $newLinkArray; 01093 } 01094 01100 public function getLanguageLinks() { 01101 return $this->mLanguageLinks; 01102 } 01103 01109 public function addCategoryLinks( $categories ) { 01110 global $wgContLang; 01111 01112 if ( !is_array( $categories ) || count( $categories ) == 0 ) { 01113 return; 01114 } 01115 01116 # Add the links to a LinkBatch 01117 $arr = array( NS_CATEGORY => $categories ); 01118 $lb = new LinkBatch; 01119 $lb->setArray( $arr ); 01120 01121 # Fetch existence plus the hiddencat property 01122 $dbr = wfGetDB( DB_SLAVE ); 01123 $res = $dbr->select( array( 'page', 'page_props' ), 01124 array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ), 01125 $lb->constructSet( 'page', $dbr ), 01126 __METHOD__, 01127 array(), 01128 array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) ) 01129 ); 01130 01131 # Add the results to the link cache 01132 $lb->addResultToCache( LinkCache::singleton(), $res ); 01133 01134 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+ 01135 $categories = array_combine( 01136 array_keys( $categories ), 01137 array_fill( 0, count( $categories ), 'normal' ) 01138 ); 01139 01140 # Mark hidden categories 01141 foreach ( $res as $row ) { 01142 if ( isset( $row->pp_value ) ) { 01143 $categories[$row->page_title] = 'hidden'; 01144 } 01145 } 01146 01147 # Add the remaining categories to the skin 01148 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) { 01149 foreach ( $categories as $category => $type ) { 01150 $origcategory = $category; 01151 $title = Title::makeTitleSafe( NS_CATEGORY, $category ); 01152 $wgContLang->findVariantLink( $category, $title, true ); 01153 if ( $category != $origcategory ) { 01154 if ( array_key_exists( $category, $categories ) ) { 01155 continue; 01156 } 01157 } 01158 $text = $wgContLang->convertHtml( $title->getText() ); 01159 $this->mCategories[] = $title->getText(); 01160 $this->mCategoryLinks[$type][] = Linker::link( $title, $text ); 01161 } 01162 } 01163 } 01164 01170 public function setCategoryLinks( $categories ) { 01171 $this->mCategoryLinks = array(); 01172 $this->addCategoryLinks( $categories ); 01173 } 01174 01183 public function getCategoryLinks() { 01184 return $this->mCategoryLinks; 01185 } 01186 01192 public function getCategories() { 01193 return $this->mCategories; 01194 } 01195 01202 public function disallowUserJs() { 01203 $this->reduceAllowedModuleOrigin( ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL ); 01204 } 01205 01213 public function isUserJsAllowed() { 01214 wfDeprecated( __METHOD__, '1.18' ); 01215 return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL; 01216 } 01217 01226 public function getAllowedModules( $type = null ) { 01227 return $this->mAllowedModuleOrigin; 01228 } 01229 01239 public function setAllowedModules( $type, $level ){ 01240 wfDeprecated( __METHOD__, '1.24' ); 01241 $this->reduceAllowedModuleOrigin( $level ); 01242 } 01243 01253 public function reduceAllowedModules( $type, $level ){ 01254 wfDeprecated( __METHOD__, '1.24' ); 01255 $this->reduceAllowedModuleOrigin( $level ); 01256 } 01257 01266 public function reduceAllowedModuleOrigin( $level ) { 01267 $this->mAllowedModuleOrigin = min( $this->mAllowedModuleOrigin, $level ); 01268 } 01269 01275 public function prependHTML( $text ) { 01276 $this->mBodytext = $text . $this->mBodytext; 01277 } 01278 01284 public function addHTML( $text ) { 01285 $this->mBodytext .= $text; 01286 } 01287 01297 public function addElement( $element, $attribs = array(), $contents = '' ) { 01298 $this->addHTML( Html::element( $element, $attribs, $contents ) ); 01299 } 01300 01304 public function clearHTML() { 01305 $this->mBodytext = ''; 01306 } 01307 01313 public function getHTML() { 01314 return $this->mBodytext; 01315 } 01316 01322 public function debug( $text ) { 01323 $this->mDebugtext .= $text; 01324 } 01325 01333 public function parserOptions( $options = null ) { 01334 if ( !$this->mParserOptions ) { 01335 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() ); 01336 $this->mParserOptions->setEditSection( false ); 01337 } 01338 return wfSetVar( $this->mParserOptions, $options ); 01339 } 01340 01348 public function setRevisionId( $revid ) { 01349 $val = is_null( $revid ) ? null : intval( $revid ); 01350 return wfSetVar( $this->mRevisionId, $val ); 01351 } 01352 01358 public function getRevisionId() { 01359 return $this->mRevisionId; 01360 } 01361 01369 public function setRevisionTimestamp( $timestmap ) { 01370 return wfSetVar( $this->mRevisionTimestamp, $timestmap ); 01371 } 01372 01379 public function getRevisionTimestamp() { 01380 return $this->mRevisionTimestamp; 01381 } 01382 01389 public function setFileVersion( $file ) { 01390 $val = null; 01391 if ( $file instanceof File && $file->exists() ) { 01392 $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ); 01393 } 01394 return wfSetVar( $this->mFileVersion, $val, true ); 01395 } 01396 01402 public function getFileVersion() { 01403 return $this->mFileVersion; 01404 } 01405 01412 public function getTemplateIds() { 01413 return $this->mTemplateIds; 01414 } 01415 01422 public function getFileSearchOptions() { 01423 return $this->mImageTimeKeys; 01424 } 01425 01434 public function addWikiText( $text, $linestart = true, $interface = true ) { 01435 $title = $this->getTitle(); // Work arround E_STRICT 01436 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface ); 01437 } 01438 01446 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) { 01447 $this->addWikiTextTitle( $text, $title, $linestart ); 01448 } 01449 01457 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) { 01458 $this->addWikiTextTitle( $text, $title, $linestart, true ); 01459 } 01460 01467 public function addWikiTextTidy( $text, $linestart = true ) { 01468 $title = $this->getTitle(); 01469 $this->addWikiTextTitleTidy( $text, $title, $linestart ); 01470 } 01471 01482 public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false, $interface = false ) { 01483 global $wgParser; 01484 01485 wfProfileIn( __METHOD__ ); 01486 01487 $popts = $this->parserOptions(); 01488 $oldTidy = $popts->setTidy( $tidy ); 01489 $popts->setInterfaceMessage( (bool) $interface ); 01490 01491 $parserOutput = $wgParser->parse( 01492 $text, $title, $popts, 01493 $linestart, true, $this->mRevisionId 01494 ); 01495 01496 $popts->setTidy( $oldTidy ); 01497 01498 $this->addParserOutput( $parserOutput ); 01499 01500 wfProfileOut( __METHOD__ ); 01501 } 01502 01508 public function addParserOutputNoText( &$parserOutput ) { 01509 $this->mLanguageLinks += $parserOutput->getLanguageLinks(); 01510 $this->addCategoryLinks( $parserOutput->getCategories() ); 01511 $this->mNewSectionLink = $parserOutput->getNewSection(); 01512 $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); 01513 01514 $this->mParseWarnings = $parserOutput->getWarnings(); 01515 if ( !$parserOutput->isCacheable() ) { 01516 $this->enableClientCache( false ); 01517 } 01518 $this->mNoGallery = $parserOutput->getNoGallery(); 01519 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() ); 01520 $this->addModules( $parserOutput->getModules() ); 01521 $this->addModuleScripts( $parserOutput->getModuleScripts() ); 01522 $this->addModuleStyles( $parserOutput->getModuleStyles() ); 01523 $this->addModuleMessages( $parserOutput->getModuleMessages() ); 01524 $this->mPreventClickjacking = $this->mPreventClickjacking 01525 || $parserOutput->preventClickjacking(); 01526 01527 // Template versioning... 01528 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) { 01529 if ( isset( $this->mTemplateIds[$ns] ) ) { 01530 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns]; 01531 } else { 01532 $this->mTemplateIds[$ns] = $dbks; 01533 } 01534 } 01535 // File versioning... 01536 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) { 01537 $this->mImageTimeKeys[$dbk] = $data; 01538 } 01539 01540 // Hooks registered in the object 01541 global $wgParserOutputHooks; 01542 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) { 01543 list( $hookName, $data ) = $hookInfo; 01544 if ( isset( $wgParserOutputHooks[$hookName] ) ) { 01545 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data ); 01546 } 01547 } 01548 01549 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); 01550 } 01551 01557 function addParserOutput( &$parserOutput ) { 01558 $this->addParserOutputNoText( $parserOutput ); 01559 $text = $parserOutput->getText(); 01560 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) ); 01561 $this->addHTML( $text ); 01562 } 01563 01564 01570 public function addTemplate( &$template ) { 01571 ob_start(); 01572 $template->execute(); 01573 $this->addHTML( ob_get_contents() ); 01574 ob_end_clean(); 01575 } 01576 01590 public function parse( $text, $linestart = true, $interface = false, $language = null ) { 01591 global $wgParser; 01592 01593 if( is_null( $this->getTitle() ) ) { 01594 throw new MWException( 'Empty $mTitle in ' . __METHOD__ ); 01595 } 01596 01597 $popts = $this->parserOptions(); 01598 if ( $interface ) { 01599 $popts->setInterfaceMessage( true ); 01600 } 01601 if ( $language !== null ) { 01602 $oldLang = $popts->setTargetLanguage( $language ); 01603 } 01604 01605 $parserOutput = $wgParser->parse( 01606 $text, $this->getTitle(), $popts, 01607 $linestart, true, $this->mRevisionId 01608 ); 01609 01610 if ( $interface ) { 01611 $popts->setInterfaceMessage( false ); 01612 } 01613 if ( $language !== null ) { 01614 $popts->setTargetLanguage( $oldLang ); 01615 } 01616 01617 return $parserOutput->getText(); 01618 } 01619 01630 public function parseInline( $text, $linestart = true, $interface = false ) { 01631 $parsed = $this->parse( $text, $linestart, $interface ); 01632 01633 $m = array(); 01634 if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) { 01635 $parsed = $m[1]; 01636 } 01637 01638 return $parsed; 01639 } 01640 01646 public function setSquidMaxage( $maxage ) { 01647 $this->mSquidMaxage = $maxage; 01648 } 01649 01657 public function enableClientCache( $state ) { 01658 return wfSetVar( $this->mEnableClientCache, $state ); 01659 } 01660 01666 function getCacheVaryCookies() { 01667 global $wgCookiePrefix, $wgCacheVaryCookies; 01668 static $cookies; 01669 if ( $cookies === null ) { 01670 $cookies = array_merge( 01671 array( 01672 "{$wgCookiePrefix}Token", 01673 "{$wgCookiePrefix}LoggedOut", 01674 session_name() 01675 ), 01676 $wgCacheVaryCookies 01677 ); 01678 wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) ); 01679 } 01680 return $cookies; 01681 } 01682 01689 function uncacheableBecauseRequestVars() { 01690 $request = $this->getRequest(); 01691 return $request->getText( 'useskin', false ) === false 01692 && $request->getText( 'uselang', false ) === false; 01693 } 01694 01701 function haveCacheVaryCookies() { 01702 $cookieHeader = $this->getRequest()->getHeader( 'cookie' ); 01703 if ( $cookieHeader === false ) { 01704 return false; 01705 } 01706 $cvCookies = $this->getCacheVaryCookies(); 01707 foreach ( $cvCookies as $cookieName ) { 01708 # Check for a simple string match, like the way squid does it 01709 if ( strpos( $cookieHeader, $cookieName ) !== false ) { 01710 wfDebug( __METHOD__ . ": found $cookieName\n" ); 01711 return true; 01712 } 01713 } 01714 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" ); 01715 return false; 01716 } 01717 01726 public function addVaryHeader( $header, $option = null ) { 01727 if ( !array_key_exists( $header, $this->mVaryHeader ) ) { 01728 $this->mVaryHeader[$header] = (array)$option; 01729 } elseif( is_array( $option ) ) { 01730 if( is_array( $this->mVaryHeader[$header] ) ) { 01731 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option ); 01732 } else { 01733 $this->mVaryHeader[$header] = $option; 01734 } 01735 } 01736 $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] ); 01737 } 01738 01744 public function getXVO() { 01745 $cvCookies = $this->getCacheVaryCookies(); 01746 01747 $cookiesOption = array(); 01748 foreach ( $cvCookies as $cookieName ) { 01749 $cookiesOption[] = 'string-contains=' . $cookieName; 01750 } 01751 $this->addVaryHeader( 'Cookie', $cookiesOption ); 01752 01753 $headers = array(); 01754 foreach( $this->mVaryHeader as $header => $option ) { 01755 $newheader = $header; 01756 if( is_array( $option ) ) { 01757 $newheader .= ';' . implode( ';', $option ); 01758 } 01759 $headers[] = $newheader; 01760 } 01761 $xvo = 'X-Vary-Options: ' . implode( ',', $headers ); 01762 01763 return $xvo; 01764 } 01765 01774 function addAcceptLanguage() { 01775 $lang = $this->getTitle()->getPageLanguage(); 01776 if( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) { 01777 $variants = $lang->getVariants(); 01778 $aloption = array(); 01779 foreach ( $variants as $variant ) { 01780 if( $variant === $lang->getCode() ) { 01781 continue; 01782 } else { 01783 $aloption[] = 'string-contains=' . $variant; 01784 01785 // IE and some other browsers use another form of language code 01786 // in their Accept-Language header, like "zh-CN" or "zh-TW". 01787 // We should handle these too. 01788 $ievariant = explode( '-', $variant ); 01789 if ( count( $ievariant ) == 2 ) { 01790 $ievariant[1] = strtoupper( $ievariant[1] ); 01791 $ievariant = implode( '-', $ievariant ); 01792 $aloption[] = 'string-contains=' . $ievariant; 01793 } 01794 } 01795 } 01796 $this->addVaryHeader( 'Accept-Language', $aloption ); 01797 } 01798 } 01799 01810 public function preventClickjacking( $enable = true ) { 01811 $this->mPreventClickjacking = $enable; 01812 } 01813 01819 public function allowClickjacking() { 01820 $this->mPreventClickjacking = false; 01821 } 01822 01829 public function getPreventClickjacking() { 01830 return $this->mPreventClickjacking; 01831 } 01832 01840 public function getFrameOptions() { 01841 global $wgBreakFrames, $wgEditPageFrameOptions; 01842 if ( $wgBreakFrames ) { 01843 return 'DENY'; 01844 } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) { 01845 return $wgEditPageFrameOptions; 01846 } 01847 return false; 01848 } 01849 01853 public function sendCacheControl() { 01854 global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO; 01855 01856 $response = $this->getRequest()->response(); 01857 if ( $wgUseETag && $this->mETag ) { 01858 $response->header( "ETag: $this->mETag" ); 01859 } 01860 01861 $this->addAcceptLanguage(); 01862 01863 # don't serve compressed data to clients who can't handle it 01864 # maintain different caches for logged-in users and non-logged in ones 01865 $response->header( 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ) ); 01866 01867 if ( $wgUseXVO ) { 01868 # Add an X-Vary-Options header for Squid with Wikimedia patches 01869 $response->header( $this->getXVO() ); 01870 } 01871 01872 if( !$this->uncacheableBecauseRequestVars() && $this->mEnableClientCache ) { 01873 if( 01874 $wgUseSquid && session_id() == '' && !$this->isPrintable() && 01875 $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() 01876 ) 01877 { 01878 if ( $wgUseESI ) { 01879 # We'll purge the proxy cache explicitly, but require end user agents 01880 # to revalidate against the proxy on each visit. 01881 # Surrogate-Control controls our Squid, Cache-Control downstream caches 01882 wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false ); 01883 # start with a shorter timeout for initial testing 01884 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"'); 01885 $response->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"'); 01886 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); 01887 } else { 01888 # We'll purge the proxy cache for anons explicitly, but require end user agents 01889 # to revalidate against the proxy on each visit. 01890 # IMPORTANT! The Squid needs to replace the Cache-Control header with 01891 # Cache-Control: s-maxage=0, must-revalidate, max-age=0 01892 wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false ); 01893 # start with a shorter timeout for initial testing 01894 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" ); 01895 $response->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' ); 01896 } 01897 } else { 01898 # We do want clients to cache if they can, but they *must* check for updates 01899 # on revisiting the page. 01900 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false ); 01901 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01902 $response->header( "Cache-Control: private, must-revalidate, max-age=0" ); 01903 } 01904 if($this->mLastModified) { 01905 $response->header( "Last-Modified: {$this->mLastModified}" ); 01906 } 01907 } else { 01908 wfDebug( __METHOD__ . ": no caching **\n", false ); 01909 01910 # In general, the absence of a last modified header should be enough to prevent 01911 # the client from using its cache. We send a few other things just to make sure. 01912 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01913 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); 01914 $response->header( 'Pragma: no-cache' ); 01915 } 01916 } 01917 01927 public static function getStatusMessage( $code ) { 01928 wfDeprecated( __METHOD__ ); 01929 return HttpStatus::getMessage( $code ); 01930 } 01931 01936 public function output() { 01937 global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP; 01938 01939 if( $this->mDoNothing ) { 01940 return; 01941 } 01942 01943 wfProfileIn( __METHOD__ ); 01944 01945 $response = $this->getRequest()->response(); 01946 01947 if ( $this->mRedirect != '' ) { 01948 # Standards require redirect URLs to be absolute 01949 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT ); 01950 01951 $redirect = $this->mRedirect; 01952 $code = $this->mRedirectCode; 01953 01954 if( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) { 01955 if( $code == '301' || $code == '303' ) { 01956 if( !$wgDebugRedirects ) { 01957 $message = HttpStatus::getMessage( $code ); 01958 $response->header( "HTTP/1.1 $code $message" ); 01959 } 01960 $this->mLastModified = wfTimestamp( TS_RFC2822 ); 01961 } 01962 if ( $wgVaryOnXFP ) { 01963 $this->addVaryHeader( 'X-Forwarded-Proto' ); 01964 } 01965 $this->sendCacheControl(); 01966 01967 $response->header( "Content-Type: text/html; charset=utf-8" ); 01968 if( $wgDebugRedirects ) { 01969 $url = htmlspecialchars( $redirect ); 01970 print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n"; 01971 print "<p>Location: <a href=\"$url\">$url</a></p>\n"; 01972 print "</body>\n</html>\n"; 01973 } else { 01974 $response->header( 'Location: ' . $redirect ); 01975 } 01976 } 01977 01978 wfProfileOut( __METHOD__ ); 01979 return; 01980 } elseif ( $this->mStatusCode ) { 01981 $message = HttpStatus::getMessage( $this->mStatusCode ); 01982 if ( $message ) { 01983 $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message ); 01984 } 01985 } 01986 01987 # Buffer output; final headers may depend on later processing 01988 ob_start(); 01989 01990 $response->header( "Content-type: $wgMimeType; charset=UTF-8" ); 01991 $response->header( 'Content-language: ' . $wgLanguageCode ); 01992 01993 // Prevent framing, if requested 01994 $frameOptions = $this->getFrameOptions(); 01995 if ( $frameOptions ) { 01996 $response->header( "X-Frame-Options: $frameOptions" ); 01997 } 01998 01999 if ( $this->mArticleBodyOnly ) { 02000 $this->out( $this->mBodytext ); 02001 } else { 02002 $this->addDefaultModules(); 02003 02004 $sk = $this->getSkin(); 02005 02006 // Hook that allows last minute changes to the output page, e.g. 02007 // adding of CSS or Javascript by extensions. 02008 wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) ); 02009 02010 wfProfileIn( 'Output-skin' ); 02011 $sk->outputPage(); 02012 wfProfileOut( 'Output-skin' ); 02013 } 02014 02015 $this->sendCacheControl(); 02016 ob_end_flush(); 02017 wfProfileOut( __METHOD__ ); 02018 } 02019 02025 public function out( $ins ) { 02026 print $ins; 02027 } 02028 02033 function blockedPage() { 02034 throw new UserBlockedError( $this->getUser()->mBlock ); 02035 } 02036 02047 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) { 02048 if ( $this->getTitle() ) { 02049 $this->mDebugtext .= 'Original title: ' . $this->getTitle()->getPrefixedText() . "\n"; 02050 } 02051 02052 $this->setPageTitle( $pageTitle ); 02053 if ( $htmlTitle !== false ) { 02054 $this->setHTMLTitle( $htmlTitle ); 02055 } 02056 $this->setRobotPolicy( 'noindex,nofollow' ); 02057 $this->setArticleRelated( false ); 02058 $this->enableClientCache( false ); 02059 $this->mRedirect = ''; 02060 $this->clearSubtitle(); 02061 $this->clearHTML(); 02062 } 02063 02074 public function showErrorPage( $title, $msg, $params = array() ) { 02075 $this->prepareErrorPage( $this->msg( $title ), $this->msg( 'errorpagetitle' ) ); 02076 02077 if ( $msg instanceof Message ){ 02078 $this->addHTML( $msg->parse() ); 02079 } else { 02080 $this->addWikiMsgArray( $msg, $params ); 02081 } 02082 02083 $this->returnToMain(); 02084 } 02085 02092 public function showPermissionsErrorPage( $errors, $action = null ) { 02093 global $wgGroupPermissions; 02094 02095 // For some action (read, edit, create and upload), display a "login to do this action" 02096 // error if all of the following conditions are met: 02097 // 1. the user is not logged in 02098 // 2. the only error is insufficient permissions (i.e. no block or something else) 02099 // 3. the error can be avoided simply by logging in 02100 if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) ) 02101 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] ) 02102 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' ) 02103 && ( ( isset( $wgGroupPermissions['user'][$action] ) && $wgGroupPermissions['user'][$action] ) 02104 || ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['autoconfirmed'][$action] ) ) 02105 ) { 02106 $displayReturnto = null; 02107 02108 # Due to bug 32276, if a user does not have read permissions, 02109 # $this->getTitle() will just give Special:Badtitle, which is 02110 # not especially useful as a returnto parameter. Use the title 02111 # from the request instead, if there was one. 02112 $request = $this->getRequest(); 02113 $returnto = Title::newFromURL( $request->getVal( 'title', '' ) ); 02114 if ( $action == 'edit' ) { 02115 $msg = 'whitelistedittext'; 02116 $displayReturnto = $returnto; 02117 } elseif ( $action == 'createpage' || $action == 'createtalk' ) { 02118 $msg = 'nocreatetext'; 02119 } elseif ( $action == 'upload' ) { 02120 $msg = 'uploadnologintext'; 02121 } else { # Read 02122 $msg = 'loginreqpagetext'; 02123 $displayReturnto = Title::newMainPage(); 02124 } 02125 02126 $query = array(); 02127 02128 if ( $returnto ) { 02129 $query['returnto'] = $returnto->getPrefixedText(); 02130 02131 if ( !$request->wasPosted() ) { 02132 $returntoquery = $request->getValues(); 02133 unset( $returntoquery['title'] ); 02134 unset( $returntoquery['returnto'] ); 02135 unset( $returntoquery['returntoquery'] ); 02136 $query['returntoquery'] = wfArrayToCGI( $returntoquery ); 02137 } 02138 } 02139 $loginLink = Linker::linkKnown( 02140 SpecialPage::getTitleFor( 'Userlogin' ), 02141 $this->msg( 'loginreqlink' )->escaped(), 02142 array(), 02143 $query 02144 ); 02145 02146 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) ); 02147 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() ); 02148 02149 # Don't return to a page the user can't read otherwise 02150 # we'll end up in a pointless loop 02151 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) { 02152 $this->returnToMain( null, $displayReturnto ); 02153 } 02154 } else { 02155 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) ); 02156 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) ); 02157 } 02158 } 02159 02166 public function versionRequired( $version ) { 02167 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) ); 02168 02169 $this->addWikiMsg( 'versionrequiredtext', $version ); 02170 $this->returnToMain(); 02171 } 02172 02178 public function permissionRequired( $permission ) { 02179 throw new PermissionsError( $permission ); 02180 } 02181 02187 public function loginToUse() { 02188 throw new PermissionsError( 'read' ); 02189 } 02190 02198 public function formatPermissionsErrorMessage( $errors, $action = null ) { 02199 if ( $action == null ) { 02200 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n"; 02201 } else { 02202 $action_desc = $this->msg( "action-$action" )->plain(); 02203 $text = $this->msg( 02204 'permissionserrorstext-withaction', 02205 count( $errors ), 02206 $action_desc 02207 )->plain() . "\n\n"; 02208 } 02209 02210 if ( count( $errors ) > 1 ) { 02211 $text .= '<ul class="permissions-errors">' . "\n"; 02212 02213 foreach( $errors as $error ) { 02214 $text .= '<li>'; 02215 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain(); 02216 $text .= "</li>\n"; 02217 } 02218 $text .= '</ul>'; 02219 } else { 02220 $text .= "<div class=\"permissions-errors\">\n" . 02221 call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() . 02222 "\n</div>"; 02223 } 02224 02225 return $text; 02226 } 02227 02248 public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) { 02249 $this->setRobotPolicy( 'noindex,nofollow' ); 02250 $this->setArticleRelated( false ); 02251 02252 // If no reason is given, just supply a default "I can't let you do 02253 // that, Dave" message. Should only occur if called by legacy code. 02254 if ( $protected && empty( $reasons ) ) { 02255 $reasons[] = array( 'badaccess-group0' ); 02256 } 02257 02258 if ( !empty( $reasons ) ) { 02259 // Permissions error 02260 if( $source ) { 02261 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) ); 02262 $this->addBacklinkSubtitle( $this->getTitle() ); 02263 } else { 02264 $this->setPageTitle( $this->msg( 'badaccess' ) ); 02265 } 02266 $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) ); 02267 } else { 02268 // Wiki is read only 02269 throw new ReadOnlyError; 02270 } 02271 02272 // Show source, if supplied 02273 if( is_string( $source ) ) { 02274 $this->addWikiMsg( 'viewsourcetext' ); 02275 02276 $pageLang = $this->getTitle()->getPageLanguage(); 02277 $params = array( 02278 'id' => 'wpTextbox1', 02279 'name' => 'wpTextbox1', 02280 'cols' => $this->getUser()->getOption( 'cols' ), 02281 'rows' => $this->getUser()->getOption( 'rows' ), 02282 'readonly' => 'readonly', 02283 'lang' => $pageLang->getHtmlCode(), 02284 'dir' => $pageLang->getDir(), 02285 ); 02286 $this->addHTML( Html::element( 'textarea', $params, $source ) ); 02287 02288 // Show templates used by this article 02289 $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() ); 02290 $this->addHTML( "<div class='templatesUsed'> 02291 $templates 02292 </div> 02293 " ); 02294 } 02295 02296 # If the title doesn't exist, it's fairly pointless to print a return 02297 # link to it. After all, you just tried editing it and couldn't, so 02298 # what's there to do there? 02299 if( $this->getTitle()->exists() ) { 02300 $this->returnToMain( null, $this->getTitle() ); 02301 } 02302 } 02303 02308 public function rateLimited() { 02309 throw new ThrottledError; 02310 } 02311 02321 public function showLagWarning( $lag ) { 02322 global $wgSlaveLagWarning, $wgSlaveLagCritical; 02323 if( $lag >= $wgSlaveLagWarning ) { 02324 $message = $lag < $wgSlaveLagCritical 02325 ? 'lag-warn-normal' 02326 : 'lag-warn-high'; 02327 $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" ); 02328 $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) ); 02329 } 02330 } 02331 02332 public function showFatalError( $message ) { 02333 $this->prepareErrorPage( $this->msg( 'internalerror' ) ); 02334 02335 $this->addHTML( $message ); 02336 } 02337 02338 public function showUnexpectedValueError( $name, $val ) { 02339 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() ); 02340 } 02341 02342 public function showFileCopyError( $old, $new ) { 02343 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() ); 02344 } 02345 02346 public function showFileRenameError( $old, $new ) { 02347 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() ); 02348 } 02349 02350 public function showFileDeleteError( $name ) { 02351 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() ); 02352 } 02353 02354 public function showFileNotFoundError( $name ) { 02355 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() ); 02356 } 02357 02365 public function addReturnTo( $title, $query = array(), $text = null ) { 02366 $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL() ) ); 02367 $link = $this->msg( 'returnto' )->rawParams( 02368 Linker::link( $title, $text, array(), $query ) )->escaped(); 02369 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" ); 02370 } 02371 02380 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) { 02381 if ( $returnto == null ) { 02382 $returnto = $this->getRequest()->getText( 'returnto' ); 02383 } 02384 02385 if ( $returntoquery == null ) { 02386 $returntoquery = $this->getRequest()->getText( 'returntoquery' ); 02387 } 02388 02389 if ( $returnto === '' ) { 02390 $returnto = Title::newMainPage(); 02391 } 02392 02393 if ( is_object( $returnto ) ) { 02394 $titleObj = $returnto; 02395 } else { 02396 $titleObj = Title::newFromText( $returnto ); 02397 } 02398 if ( !is_object( $titleObj ) ) { 02399 $titleObj = Title::newMainPage(); 02400 } 02401 02402 $this->addReturnTo( $titleObj, $returntoquery ); 02403 } 02404 02410 public function headElement( Skin $sk, $includeStyle = true ) { 02411 global $wgContLang; 02412 02413 $userdir = $this->getLanguage()->getDir(); 02414 $sitedir = $wgContLang->getDir(); 02415 02416 if ( $sk->commonPrintStylesheet() ) { 02417 $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' ); 02418 } 02419 02420 $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) ); 02421 02422 if ( $this->getHTMLTitle() == '' ) { 02423 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) ); 02424 } 02425 02426 $openHead = Html::openElement( 'head' ); 02427 if ( $openHead ) { 02428 # Don't bother with the newline if $head == '' 02429 $ret .= "$openHead\n"; 02430 } 02431 02432 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n"; 02433 02434 $ret .= implode( "\n", array( 02435 $this->getHeadLinks( null, true ), 02436 $this->buildCssLinks(), 02437 $this->getHeadScripts(), 02438 $this->getHeadItems() 02439 ) ); 02440 02441 $closeHead = Html::closeElement( 'head' ); 02442 if ( $closeHead ) { 02443 $ret .= "$closeHead\n"; 02444 } 02445 02446 $bodyAttrs = array(); 02447 02448 # Classes for LTR/RTL directionality support 02449 $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir"; 02450 02451 if ( $this->getLanguage()->capitalizeAllNouns() ) { 02452 # A <body> class is probably not the best way to do this . . . 02453 $bodyAttrs['class'] .= ' capitalize-all-nouns'; 02454 } 02455 $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() ); 02456 $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() ); 02457 $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) ); 02458 02459 $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need 02460 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) ); 02461 02462 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n"; 02463 02464 return $ret; 02465 } 02466 02470 private function addDefaultModules() { 02471 global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax, 02472 $wgAjaxWatch, $wgEnableMWSuggest; 02473 02474 // Add base resources 02475 $this->addModules( array( 02476 'mediawiki.user', 02477 'mediawiki.page.startup', 02478 'mediawiki.page.ready', 02479 ) ); 02480 if ( $wgIncludeLegacyJavaScript ){ 02481 $this->addModules( 'mediawiki.legacy.wikibits' ); 02482 } 02483 02484 if ( $wgPreloadJavaScriptMwUtil ) { 02485 $this->addModules( 'mediawiki.util' ); 02486 } 02487 02488 MWDebug::addModules( $this ); 02489 02490 // Add various resources if required 02491 if ( $wgUseAjax ) { 02492 $this->addModules( 'mediawiki.legacy.ajax' ); 02493 02494 wfRunHooks( 'AjaxAddScript', array( &$this ) ); 02495 02496 if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) { 02497 $this->addModules( 'mediawiki.action.watch.ajax' ); 02498 } 02499 02500 if ( $wgEnableMWSuggest && !$this->getUser()->getOption( 'disablesuggest', false ) ) { 02501 $this->addModules( 'mediawiki.legacy.mwsuggest' ); 02502 } 02503 } 02504 02505 if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) { 02506 $this->addModules( 'mediawiki.action.view.rightClickEdit' ); 02507 } 02508 02509 # Crazy edit-on-double-click stuff 02510 if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) { 02511 $this->addModules( 'mediawiki.action.view.dblClickEdit' ); 02512 } 02513 } 02514 02520 public function getResourceLoader() { 02521 if ( is_null( $this->mResourceLoader ) ) { 02522 $this->mResourceLoader = new ResourceLoader(); 02523 } 02524 return $this->mResourceLoader; 02525 } 02526 02536 protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) { 02537 global $wgResourceLoaderUseESI; 02538 02539 if ( !count( $modules ) ) { 02540 return ''; 02541 } 02542 02543 if ( count( $modules ) > 1 ) { 02544 // Remove duplicate module requests 02545 $modules = array_unique( (array) $modules ); 02546 // Sort module names so requests are more uniform 02547 sort( $modules ); 02548 02549 if ( ResourceLoader::inDebugMode() ) { 02550 // Recursively call us for every item 02551 $links = ''; 02552 foreach ( $modules as $name ) { 02553 $links .= $this->makeResourceLoaderLink( $name, $only, $useESI ); 02554 } 02555 return $links; 02556 } 02557 } 02558 02559 // Create keyed-by-group list of module objects from modules list 02560 $groups = array(); 02561 $resourceLoader = $this->getResourceLoader(); 02562 foreach ( (array) $modules as $name ) { 02563 $module = $resourceLoader->getModule( $name ); 02564 # Check that we're allowed to include this module on this page 02565 if ( !$module 02566 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) 02567 && $only == ResourceLoaderModule::TYPE_SCRIPTS ) 02568 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES ) 02569 && $only == ResourceLoaderModule::TYPE_STYLES ) 02570 ) 02571 { 02572 continue; 02573 } 02574 02575 $group = $module->getGroup(); 02576 if ( !isset( $groups[$group] ) ) { 02577 $groups[$group] = array(); 02578 } 02579 $groups[$group][$name] = $module; 02580 } 02581 02582 $links = ''; 02583 foreach ( $groups as $group => $modules ) { 02584 // Special handling for user-specific groups 02585 $user = null; 02586 if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) { 02587 $user = $this->getUser()->getName(); 02588 } 02589 02590 // Create a fake request based on the one we are about to make so modules return 02591 // correct timestamp and emptiness data 02592 $query = ResourceLoader::makeLoaderQuery( 02593 array(), // modules; not determined yet 02594 $this->getLanguage()->getCode(), 02595 $this->getSkin()->getSkinName(), 02596 $user, 02597 null, // version; not determined yet 02598 ResourceLoader::inDebugMode(), 02599 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02600 $this->isPrintable(), 02601 $this->getRequest()->getBool( 'handheld' ), 02602 $extraQuery 02603 ); 02604 $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); 02605 // Drop modules that know they're empty 02606 foreach ( $modules as $key => $module ) { 02607 if ( $module->isKnownEmpty( $context ) ) { 02608 unset( $modules[$key] ); 02609 } 02610 } 02611 // If there are no modules left, skip this group 02612 if ( $modules === array() ) { 02613 continue; 02614 } 02615 02616 // Inline private modules. These can't be loaded through load.php for security 02617 // reasons, see bug 34907. Note that these modules should be loaded from 02618 // getHeadScripts() before the first loader call. Otherwise other modules can't 02619 // properly use them as dependencies (bug 30914) 02620 if ( $group === 'private' ) { 02621 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02622 $links .= Html::inlineStyle( 02623 $resourceLoader->makeModuleResponse( $context, $modules ) 02624 ); 02625 } else { 02626 $links .= Html::inlineScript( 02627 ResourceLoader::makeLoaderConditionalScript( 02628 $resourceLoader->makeModuleResponse( $context, $modules ) 02629 ) 02630 ); 02631 } 02632 $links .= "\n"; 02633 continue; 02634 } 02635 // Special handling for the user group; because users might change their stuff 02636 // on-wiki like user pages, or user preferences; we need to find the highest 02637 // timestamp of these user-changable modules so we can ensure cache misses on change 02638 // This should NOT be done for the site group (bug 27564) because anons get that too 02639 // and we shouldn't be putting timestamps in Squid-cached HTML 02640 $version = null; 02641 if ( $group === 'user' ) { 02642 // Get the maximum timestamp 02643 $timestamp = 1; 02644 foreach ( $modules as $module ) { 02645 $timestamp = max( $timestamp, $module->getModifiedTime( $context ) ); 02646 } 02647 // Add a version parameter so cache will break when things change 02648 $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp ); 02649 } 02650 02651 $url = ResourceLoader::makeLoaderURL( 02652 array_keys( $modules ), 02653 $this->getLanguage()->getCode(), 02654 $this->getSkin()->getSkinName(), 02655 $user, 02656 $version, 02657 ResourceLoader::inDebugMode(), 02658 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02659 $this->isPrintable(), 02660 $this->getRequest()->getBool( 'handheld' ), 02661 $extraQuery 02662 ); 02663 if ( $useESI && $wgResourceLoaderUseESI ) { 02664 $esi = Xml::element( 'esi:include', array( 'src' => $url ) ); 02665 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02666 $link = Html::inlineStyle( $esi ); 02667 } else { 02668 $link = Html::inlineScript( $esi ); 02669 } 02670 } else { 02671 // Automatically select style/script elements 02672 if ( $only === ResourceLoaderModule::TYPE_STYLES ) { 02673 $link = Html::linkedStyle( $url ); 02674 } else if ( $loadCall ) { 02675 $link = Html::inlineScript( 02676 ResourceLoader::makeLoaderConditionalScript( 02677 Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) ) 02678 ) 02679 ); 02680 } else { 02681 $link = Html::linkedScript( $url ); 02682 } 02683 } 02684 02685 if( $group == 'noscript' ){ 02686 $links .= Html::rawElement( 'noscript', array(), $link ) . "\n"; 02687 } else { 02688 $links .= $link . "\n"; 02689 } 02690 } 02691 return $links; 02692 } 02693 02700 function getHeadScripts() { 02701 global $wgResourceLoaderExperimentalAsyncLoading; 02702 02703 // Startup - this will immediately load jquery and mediawiki modules 02704 $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true ); 02705 02706 // Load config before anything else 02707 $scripts .= Html::inlineScript( 02708 ResourceLoader::makeLoaderConditionalScript( 02709 ResourceLoader::makeConfigSetScript( $this->getJSVars() ) 02710 ) 02711 ); 02712 02713 // Load embeddable private modules before any loader links 02714 // This needs to be TYPE_COMBINED so these modules are properly wrapped 02715 // in mw.loader.implement() calls and deferred until mw.user is available 02716 $embedScripts = array( 'user.options', 'user.tokens' ); 02717 $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED ); 02718 02719 // Script and Messages "only" requests marked for top inclusion 02720 // Messages should go first 02721 $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES ); 02722 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS ); 02723 02724 // Modules requests - let the client calculate dependencies and batch requests as it likes 02725 // Only load modules that have marked themselves for loading at the top 02726 $modules = $this->getModules( true, 'top' ); 02727 if ( $modules ) { 02728 $scripts .= Html::inlineScript( 02729 ResourceLoader::makeLoaderConditionalScript( 02730 Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) 02731 ) 02732 ); 02733 } 02734 02735 if ( $wgResourceLoaderExperimentalAsyncLoading ) { 02736 $scripts .= $this->getScriptsForBottomQueue( true ); 02737 } 02738 02739 return $scripts; 02740 } 02741 02751 function getScriptsForBottomQueue( $inHead ) { 02752 global $wgUseSiteJs, $wgAllowUserJs; 02753 02754 // Script and Messages "only" requests marked for bottom inclusion 02755 // If we're in the <head>, use load() calls rather than <script src="..."> tags 02756 // Messages should go first 02757 $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), 02758 ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(), 02759 /* $loadCall = */ $inHead 02760 ); 02761 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), 02762 ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(), 02763 /* $loadCall = */ $inHead 02764 ); 02765 02766 // Modules requests - let the client calculate dependencies and batch requests as it likes 02767 // Only load modules that have marked themselves for loading at the bottom 02768 $modules = $this->getModules( true, 'bottom' ); 02769 if ( $modules ) { 02770 $scripts .= Html::inlineScript( 02771 ResourceLoader::makeLoaderConditionalScript( 02772 Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) ) 02773 ) 02774 ); 02775 } 02776 02777 // Legacy Scripts 02778 $scripts .= "\n" . $this->mScripts; 02779 02780 $userScripts = array(); 02781 02782 // Add site JS if enabled 02783 if ( $wgUseSiteJs ) { 02784 $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS, 02785 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02786 ); 02787 if( $this->getUser()->isLoggedIn() ){ 02788 $userScripts[] = 'user.groups'; 02789 } 02790 } 02791 02792 // Add user JS if enabled 02793 if ( $wgAllowUserJs && $this->getUser()->isLoggedIn() ) { 02794 if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) { 02795 # XXX: additional security check/prompt? 02796 // We're on a preview of a JS subpage 02797 // Exclude this page from the user module in case it's in there (bug 26283) 02798 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false, 02799 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead 02800 ); 02801 // Load the previewed JS 02802 $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n"; 02803 } else { 02804 // Include the user module normally 02805 // We can't do $userScripts[] = 'user'; because the user module would end up 02806 // being wrapped in a closure, so load it raw like 'site' 02807 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, 02808 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02809 ); 02810 } 02811 } 02812 $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED, 02813 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02814 ); 02815 02816 return $scripts; 02817 } 02818 02822 function getBottomScripts() { 02823 global $wgResourceLoaderExperimentalAsyncLoading; 02824 if ( !$wgResourceLoaderExperimentalAsyncLoading ) { 02825 return $this->getScriptsForBottomQueue( false ); 02826 } else { 02827 return ''; 02828 } 02829 } 02830 02837 public function addJsConfigVars( $keys, $value = null ) { 02838 if ( is_array( $keys ) ) { 02839 foreach ( $keys as $key => $value ) { 02840 $this->mJsConfigVars[$key] = $value; 02841 } 02842 return; 02843 } 02844 02845 $this->mJsConfigVars[$keys] = $value; 02846 } 02847 02848 02861 public function getJSVars() { 02862 global $wgUseAjax, $wgEnableMWSuggest; 02863 02864 $latestRevID = 0; 02865 $pageID = 0; 02866 $canonicalName = false; # bug 21115 02867 02868 $title = $this->getTitle(); 02869 $ns = $title->getNamespace(); 02870 $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText(); 02871 02872 // Get the relevant title so that AJAX features can use the correct page name 02873 // when making API requests from certain special pages (bug 34972). 02874 $relevantTitle = $this->getSkin()->getRelevantTitle(); 02875 02876 if ( $ns == NS_SPECIAL ) { 02877 list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); 02878 } elseif ( $this->canUseWikiPage() ) { 02879 $wikiPage = $this->getWikiPage(); 02880 $latestRevID = $wikiPage->getLatest(); 02881 $pageID = $wikiPage->getId(); 02882 } 02883 02884 $lang = $title->getPageLanguage(); 02885 02886 // Pre-process information 02887 $separatorTransTable = $lang->separatorTransformTable(); 02888 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); 02889 $compactSeparatorTransTable = array( 02890 implode( "\t", array_keys( $separatorTransTable ) ), 02891 implode( "\t", $separatorTransTable ), 02892 ); 02893 $digitTransTable = $lang->digitTransformTable(); 02894 $digitTransTable = $digitTransTable ? $digitTransTable : array(); 02895 $compactDigitTransTable = array( 02896 implode( "\t", array_keys( $digitTransTable ) ), 02897 implode( "\t", $digitTransTable ), 02898 ); 02899 02900 $vars = array( 02901 'wgCanonicalNamespace' => $nsname, 02902 'wgCanonicalSpecialPageName' => $canonicalName, 02903 'wgNamespaceNumber' => $title->getNamespace(), 02904 'wgPageName' => $title->getPrefixedDBKey(), 02905 'wgTitle' => $title->getText(), 02906 'wgCurRevisionId' => $latestRevID, 02907 'wgArticleId' => $pageID, 02908 'wgIsArticle' => $this->isArticle(), 02909 'wgAction' => Action::getActionName( $this->getContext() ), 02910 'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(), 02911 'wgUserGroups' => $this->getUser()->getEffectiveGroups(), 02912 'wgCategories' => $this->getCategories(), 02913 'wgBreakFrames' => $this->getFrameOptions() == 'DENY', 02914 'wgPageContentLanguage' => $lang->getCode(), 02915 'wgSeparatorTransformTable' => $compactSeparatorTransTable, 02916 'wgDigitTransformTable' => $compactDigitTransTable, 02917 'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(), 02918 ); 02919 if ( $lang->hasVariants() ) { 02920 $vars['wgUserVariant'] = $lang->getPreferredVariant(); 02921 } 02922 foreach ( $title->getRestrictionTypes() as $type ) { 02923 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type ); 02924 } 02925 if ( $wgUseAjax && $wgEnableMWSuggest && !$this->getUser()->getOption( 'disablesuggest', false ) ) { 02926 $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $this->getUser() ); 02927 } 02928 if ( $title->isMainPage() ) { 02929 $vars['wgIsMainPage'] = true; 02930 } 02931 if ( $this->mRedirectedFrom ) { 02932 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey(); 02933 } 02934 02935 // Allow extensions to add their custom variables to the mw.config map. 02936 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not 02937 // page-dependant but site-wide (without state). 02938 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. 02939 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) ); 02940 02941 // Merge in variables from addJsConfigVars last 02942 return array_merge( $vars, $this->mJsConfigVars ); 02943 } 02944 02954 public function userCanPreview() { 02955 if ( $this->getRequest()->getVal( 'action' ) != 'submit' 02956 || !$this->getRequest()->wasPosted() 02957 || !$this->getUser()->matchEditToken( 02958 $this->getRequest()->getVal( 'wpEditToken' ) ) 02959 ) { 02960 return false; 02961 } 02962 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) { 02963 return false; 02964 } 02965 02966 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) ); 02967 } 02968 02975 public function getHeadLinks( $unused = null, $addContentType = false ) { 02976 global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI, 02977 $wgSitename, $wgVersion, $wgHtml5, $wgMimeType, 02978 $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes, 02979 $wgDisableLangConversion, $wgCanonicalLanguageLinks, 02980 $wgRightsPage, $wgRightsUrl; 02981 02982 $tags = array(); 02983 02984 if ( $addContentType ) { 02985 if ( $wgHtml5 ) { 02986 # More succinct than <meta http-equiv=Content-Type>, has the 02987 # same effect 02988 $tags[] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) ); 02989 } else { 02990 $tags[] = Html::element( 'meta', array( 02991 'http-equiv' => 'Content-Type', 02992 'content' => "$wgMimeType; charset=UTF-8" 02993 ) ); 02994 $tags[] = Html::element( 'meta', array( // bug 15835 02995 'http-equiv' => 'Content-Style-Type', 02996 'content' => 'text/css' 02997 ) ); 02998 } 02999 } 03000 03001 $tags[] = Html::element( 'meta', array( 03002 'name' => 'generator', 03003 'content' => "MediaWiki $wgVersion", 03004 ) ); 03005 03006 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}"; 03007 if( $p !== 'index,follow' ) { 03008 // http://www.robotstxt.org/wc/meta-user.html 03009 // Only show if it's different from the default robots policy 03010 $tags[] = Html::element( 'meta', array( 03011 'name' => 'robots', 03012 'content' => $p, 03013 ) ); 03014 } 03015 03016 if ( count( $this->mKeywords ) > 0 ) { 03017 $strip = array( 03018 "/<.*?" . ">/" => '', 03019 "/_/" => ' ' 03020 ); 03021 $tags[] = Html::element( 'meta', array( 03022 'name' => 'keywords', 03023 'content' => preg_replace( 03024 array_keys( $strip ), 03025 array_values( $strip ), 03026 implode( ',', $this->mKeywords ) 03027 ) 03028 ) ); 03029 } 03030 03031 foreach ( $this->mMetatags as $tag ) { 03032 if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) { 03033 $a = 'http-equiv'; 03034 $tag[0] = substr( $tag[0], 5 ); 03035 } else { 03036 $a = 'name'; 03037 } 03038 $tags[] = Html::element( 'meta', 03039 array( 03040 $a => $tag[0], 03041 'content' => $tag[1] 03042 ) 03043 ); 03044 } 03045 03046 foreach ( $this->mLinktags as $tag ) { 03047 $tags[] = Html::element( 'link', $tag ); 03048 } 03049 03050 # Universal edit button 03051 if ( $wgUniversalEditButton && $this->isArticleRelated() ) { 03052 $user = $this->getUser(); 03053 if ( $this->getTitle()->quickUserCan( 'edit', $user ) 03054 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) { 03055 // Original UniversalEditButton 03056 $msg = $this->msg( 'edit' )->text(); 03057 $tags[] = Html::element( 'link', array( 03058 'rel' => 'alternate', 03059 'type' => 'application/x-wiki', 03060 'title' => $msg, 03061 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03062 ) ); 03063 // Alternate edit link 03064 $tags[] = Html::element( 'link', array( 03065 'rel' => 'edit', 03066 'title' => $msg, 03067 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03068 ) ); 03069 } 03070 } 03071 03072 # Generally the order of the favicon and apple-touch-icon links 03073 # should not matter, but Konqueror (3.5.9 at least) incorrectly 03074 # uses whichever one appears later in the HTML source. Make sure 03075 # apple-touch-icon is specified first to avoid this. 03076 if ( $wgAppleTouchIcon !== false ) { 03077 $tags[] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) ); 03078 } 03079 03080 if ( $wgFavicon !== false ) { 03081 $tags[] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) ); 03082 } 03083 03084 # OpenSearch description link 03085 $tags[] = Html::element( 'link', array( 03086 'rel' => 'search', 03087 'type' => 'application/opensearchdescription+xml', 03088 'href' => wfScript( 'opensearch_desc' ), 03089 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(), 03090 ) ); 03091 03092 if ( $wgEnableAPI ) { 03093 # Real Simple Discovery link, provides auto-discovery information 03094 # for the MediaWiki API (and potentially additional custom API 03095 # support such as WordPress or Twitter-compatible APIs for a 03096 # blogging extension, etc) 03097 $tags[] = Html::element( 'link', array( 03098 'rel' => 'EditURI', 03099 'type' => 'application/rsd+xml', 03100 // Output a protocol-relative URL here if $wgServer is protocol-relative 03101 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though 03102 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ), 03103 ) ); 03104 } 03105 03106 03107 # Language variants 03108 if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) { 03109 $lang = $this->getTitle()->getPageLanguage(); 03110 if ( $lang->hasVariants() ) { 03111 03112 $urlvar = $lang->getURLVariant(); 03113 03114 if ( !$urlvar ) { 03115 $variants = $lang->getVariants(); 03116 foreach ( $variants as $_v ) { 03117 $tags[] = Html::element( 'link', array( 03118 'rel' => 'alternate', 03119 'hreflang' => $_v, 03120 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) ) 03121 ); 03122 } 03123 } else { 03124 $tags[] = Html::element( 'link', array( 03125 'rel' => 'canonical', 03126 'href' => $this->getTitle()->getCanonicalUrl() 03127 ) ); 03128 } 03129 } 03130 } 03131 03132 # Copyright 03133 $copyright = ''; 03134 if ( $wgRightsPage ) { 03135 $copy = Title::newFromText( $wgRightsPage ); 03136 03137 if ( $copy ) { 03138 $copyright = $copy->getLocalURL(); 03139 } 03140 } 03141 03142 if ( !$copyright && $wgRightsUrl ) { 03143 $copyright = $wgRightsUrl; 03144 } 03145 03146 if ( $copyright ) { 03147 $tags[] = Html::element( 'link', array( 03148 'rel' => 'copyright', 03149 'href' => $copyright ) 03150 ); 03151 } 03152 03153 # Feeds 03154 if ( $wgFeed ) { 03155 foreach( $this->getSyndicationLinks() as $format => $link ) { 03156 # Use the page name for the title. In principle, this could 03157 # lead to issues with having the same name for different feeds 03158 # corresponding to the same page, but we can't avoid that at 03159 # this low a level. 03160 03161 $tags[] = $this->feedLink( 03162 $format, 03163 $link, 03164 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep) 03165 $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text() 03166 ); 03167 } 03168 03169 # Recent changes feed should appear on every page (except recentchanges, 03170 # that would be redundant). Put it after the per-page feed to avoid 03171 # changing existing behavior. It's still available, probably via a 03172 # menu in your browser. Some sites might have a different feed they'd 03173 # like to promote instead of the RC feed (maybe like a "Recent New Articles" 03174 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined. 03175 # If so, use it instead. 03176 if ( $wgOverrideSiteFeed ) { 03177 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) { 03178 // Note, this->feedLink escapes the url. 03179 $tags[] = $this->feedLink( 03180 $type, 03181 $feedUrl, 03182 $this->msg( "site-{$type}-feed", $wgSitename )->text() 03183 ); 03184 } 03185 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) { 03186 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' ); 03187 foreach ( $wgAdvertisedFeedTypes as $format ) { 03188 $tags[] = $this->feedLink( 03189 $format, 03190 $rctitle->getLocalURL( "feed={$format}" ), 03191 $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'. 03192 ); 03193 } 03194 } 03195 } 03196 return implode( "\n", $tags ); 03197 } 03198 03207 private function feedLink( $type, $url, $text ) { 03208 return Html::element( 'link', array( 03209 'rel' => 'alternate', 03210 'type' => "application/$type+xml", 03211 'title' => $text, 03212 'href' => $url ) 03213 ); 03214 } 03215 03225 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) { 03226 $options = array(); 03227 // Even though we expect the media type to be lowercase, but here we 03228 // force it to lowercase to be safe. 03229 if( $media ) { 03230 $options['media'] = $media; 03231 } 03232 if( $condition ) { 03233 $options['condition'] = $condition; 03234 } 03235 if( $dir ) { 03236 $options['dir'] = $dir; 03237 } 03238 $this->styles[$style] = $options; 03239 } 03240 03246 public function addInlineStyle( $style_css, $flip = 'noflip' ) { 03247 if( $flip === 'flip' && $this->getLanguage()->isRTL() ) { 03248 # If wanted, and the interface is right-to-left, flip the CSS 03249 $style_css = CSSJanus::transform( $style_css, true, false ); 03250 } 03251 $this->mInlineStyles .= Html::inlineStyle( $style_css ); 03252 } 03253 03260 public function buildCssLinks() { 03261 global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, 03262 $wgLang, $wgContLang; 03263 03264 $this->getSkin()->setupSkinUserCss( $this ); 03265 03266 // Add ResourceLoader styles 03267 // Split the styles into four groups 03268 $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() ); 03269 $otherTags = ''; // Tags to append after the normal <link> tags 03270 $resourceLoader = $this->getResourceLoader(); 03271 03272 $moduleStyles = $this->getModuleStyles(); 03273 03274 // Per-site custom styles 03275 if ( $wgUseSiteCss ) { 03276 $moduleStyles[] = 'site'; 03277 $moduleStyles[] = 'noscript'; 03278 if( $this->getUser()->isLoggedIn() ){ 03279 $moduleStyles[] = 'user.groups'; 03280 } 03281 } 03282 03283 // Per-user custom styles 03284 if ( $wgAllowUserCss ) { 03285 if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) { 03286 // We're on a preview of a CSS subpage 03287 // Exclude this page from the user module in case it's in there (bug 26283) 03288 $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false, 03289 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ) 03290 ); 03291 03292 // Load the previewed CSS 03293 // If needed, Janus it first. This is user-supplied CSS, so it's 03294 // assumed to be right for the content language directionality. 03295 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' ); 03296 if ( $wgLang->getDir() !== $wgContLang->getDir() ) { 03297 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false ); 03298 } 03299 $otherTags .= Html::inlineStyle( $previewedCSS ); 03300 } else { 03301 // Load the user styles normally 03302 $moduleStyles[] = 'user'; 03303 } 03304 } 03305 03306 // Per-user preference styles 03307 if ( $wgAllowUserCssPrefs ) { 03308 $moduleStyles[] = 'user.cssprefs'; 03309 } 03310 03311 foreach ( $moduleStyles as $name ) { 03312 $module = $resourceLoader->getModule( $name ); 03313 if ( !$module ) { 03314 continue; 03315 } 03316 $group = $module->getGroup(); 03317 // Modules in groups named "other" or anything different than "user", "site" or "private" 03318 // will be placed in the "other" group 03319 $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name; 03320 } 03321 03322 // We want site, private and user styles to override dynamically added styles from modules, but we want 03323 // dynamically added styles to override statically added styles from other modules. So the order 03324 // has to be other, dynamic, site, private, user 03325 // Add statically added styles for other modules 03326 $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES ); 03327 // Add normal styles added through addStyle()/addInlineStyle() here 03328 $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles; 03329 // Add marker tag to mark the place where the client-side loader should inject dynamic styles 03330 // We use a <meta> tag with a made-up name for this because that's valid HTML 03331 $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n"; 03332 03333 // Add site, private and user styles 03334 // 'private' at present only contains user.options, so put that before 'user' 03335 // Any future private modules will likely have a similar user-specific character 03336 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) { 03337 $ret .= $this->makeResourceLoaderLink( $styles[$group], 03338 ResourceLoaderModule::TYPE_STYLES 03339 ); 03340 } 03341 03342 // Add stuff in $otherTags (previewed user CSS if applicable) 03343 $ret .= $otherTags; 03344 return $ret; 03345 } 03346 03350 public function buildCssLinksArray() { 03351 $links = array(); 03352 03353 // Add any extension CSS 03354 foreach ( $this->mExtStyles as $url ) { 03355 $this->addStyle( $url ); 03356 } 03357 $this->mExtStyles = array(); 03358 03359 foreach( $this->styles as $file => $options ) { 03360 $link = $this->styleLink( $file, $options ); 03361 if( $link ) { 03362 $links[$file] = $link; 03363 } 03364 } 03365 return $links; 03366 } 03367 03376 protected function styleLink( $style, $options ) { 03377 if( isset( $options['dir'] ) ) { 03378 if( $this->getLanguage()->getDir() != $options['dir'] ) { 03379 return ''; 03380 } 03381 } 03382 03383 if( isset( $options['media'] ) ) { 03384 $media = self::transformCssMedia( $options['media'] ); 03385 if( is_null( $media ) ) { 03386 return ''; 03387 } 03388 } else { 03389 $media = 'all'; 03390 } 03391 03392 if( substr( $style, 0, 1 ) == '/' || 03393 substr( $style, 0, 5 ) == 'http:' || 03394 substr( $style, 0, 6 ) == 'https:' ) { 03395 $url = $style; 03396 } else { 03397 global $wgStylePath, $wgStyleVersion; 03398 $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion; 03399 } 03400 03401 $link = Html::linkedStyle( $url, $media ); 03402 03403 if( isset( $options['condition'] ) ) { 03404 $condition = htmlspecialchars( $options['condition'] ); 03405 $link = "<!--[if $condition]>$link<![endif]-->"; 03406 } 03407 return $link; 03408 } 03409 03416 public static function transformCssMedia( $media ) { 03417 global $wgRequest, $wgHandheldForIPhone; 03418 03419 // Switch in on-screen display for media testing 03420 $switches = array( 03421 'printable' => 'print', 03422 'handheld' => 'handheld', 03423 ); 03424 foreach( $switches as $switch => $targetMedia ) { 03425 if( $wgRequest->getBool( $switch ) ) { 03426 if( $media == $targetMedia ) { 03427 $media = ''; 03428 } elseif( $media == 'screen' ) { 03429 return null; 03430 } 03431 } 03432 } 03433 03434 // Expand longer media queries as iPhone doesn't grok 'handheld' 03435 if( $wgHandheldForIPhone ) { 03436 $mediaAliases = array( 03437 'screen' => 'screen and (min-device-width: 481px)', 03438 'handheld' => 'handheld, only screen and (max-device-width: 480px)', 03439 ); 03440 03441 if( isset( $mediaAliases[$media] ) ) { 03442 $media = $mediaAliases[$media]; 03443 } 03444 } 03445 03446 return $media; 03447 } 03448 03455 public function addWikiMsg( /*...*/ ) { 03456 $args = func_get_args(); 03457 $name = array_shift( $args ); 03458 $this->addWikiMsgArray( $name, $args ); 03459 } 03460 03469 public function addWikiMsgArray( $name, $args ) { 03470 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() ); 03471 } 03472 03499 public function wrapWikiMsg( $wrap /*, ...*/ ) { 03500 $msgSpecs = func_get_args(); 03501 array_shift( $msgSpecs ); 03502 $msgSpecs = array_values( $msgSpecs ); 03503 $s = $wrap; 03504 foreach ( $msgSpecs as $n => $spec ) { 03505 $options = array(); 03506 if ( is_array( $spec ) ) { 03507 $args = $spec; 03508 $name = array_shift( $args ); 03509 if ( isset( $args['options'] ) ) { 03510 $options = $args['options']; 03511 unset( $args['options'] ); 03512 } 03513 } else { 03514 $args = array(); 03515 $name = $spec; 03516 } 03517 $s = str_replace( '$' . ( $n + 1 ), wfMsgExt( $name, $options, $args ), $s ); 03518 } 03519 $this->addWikiText( $s ); 03520 } 03521 03531 public function includeJQuery( $modules = array() ) { 03532 return array(); 03533 } 03534 03535 }