MediaWiki
REL1_20
|
00001 <?php 00038 class OutputPage extends ContextSource { 00040 var $mMetatags = array(); 00041 00043 var $mKeywords = array(); 00044 00045 var $mLinktags = array(); 00046 00048 var $mExtStyles = array(); 00049 00051 var $mPagetitle = ''; 00052 00054 var $mBodytext = ''; 00055 00061 public $mDebugtext = ''; 00062 00064 var $mHTMLtitle = ''; 00065 00067 var $mIsarticle = false; 00068 00073 var $mIsArticleRelated = true; 00074 00079 var $mPrintable = false; 00080 00087 private $mSubtitle = array(); 00088 00089 var $mRedirect = ''; 00090 var $mStatusCode; 00091 00096 var $mLastModified = ''; 00097 00108 var $mETag = false; 00109 00110 var $mCategoryLinks = array(); 00111 var $mCategories = array(); 00112 00114 var $mLanguageLinks = array(); 00115 00122 var $mScripts = ''; 00123 00127 var $mInlineStyles = ''; 00128 00129 // 00130 var $mLinkColours; 00131 00136 var $mPageLinkTitle = ''; 00137 00139 var $mHeadItems = array(); 00140 00141 // @todo FIXME: Next variables probably comes from the resource loader 00142 var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array(); 00143 var $mResourceLoader; 00144 var $mJsConfigVars = array(); 00145 00147 var $mInlineMsg = array(); 00148 00149 var $mTemplateIds = array(); 00150 var $mImageTimeKeys = array(); 00151 00152 var $mRedirectCode = ''; 00153 00154 var $mFeedLinksAppendQuery = null; 00155 00156 # What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page? 00157 # @see ResourceLoaderModule::$origin 00158 # ResourceLoaderModule::ORIGIN_ALL is assumed unless overridden; 00159 protected $mAllowedModules = array( 00160 ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL, 00161 ); 00162 00167 var $mDoNothing = false; 00168 00169 // Parser related. 00170 var $mContainsOldMagic = 0, $mContainsNewMagic = 0; 00171 00176 protected $mParserOptions = null; 00177 00184 var $mFeedLinks = array(); 00185 00186 // Gwicke work on squid caching? Roughly from 2003. 00187 var $mEnableClientCache = true; 00188 00193 var $mArticleBodyOnly = false; 00194 00195 var $mNewSectionLink = false; 00196 var $mHideNewSectionLink = false; 00197 00203 var $mNoGallery = false; 00204 00205 // should be private. 00206 var $mPageTitleActionText = ''; 00207 var $mParseWarnings = array(); 00208 00209 // Cache stuff. Looks like mEnableClientCache 00210 var $mSquidMaxage = 0; 00211 00212 // @todo document 00213 var $mPreventClickjacking = true; 00214 00216 var $mRevisionId = null; 00217 private $mRevisionTimestamp = null; 00218 00219 var $mFileVersion = null; 00220 00229 var $styles = array(); 00230 00234 protected $mJQueryDone = false; 00235 00236 private $mIndexPolicy = 'index'; 00237 private $mFollowPolicy = 'follow'; 00238 private $mVaryHeader = array( 00239 'Accept-Encoding' => array( 'list-contains=gzip' ), 00240 ); 00241 00248 private $mRedirectedFrom = null; 00249 00255 function __construct( IContextSource $context = null ) { 00256 if ( $context === null ) { 00257 # Extensions should use `new RequestContext` instead of `new OutputPage` now. 00258 wfDeprecated( __METHOD__ ); 00259 } else { 00260 $this->setContext( $context ); 00261 } 00262 } 00263 00270 public function redirect( $url, $responsecode = '302' ) { 00271 # Strip newlines as a paranoia check for header injection in PHP<5.1.2 00272 $this->mRedirect = str_replace( "\n", '', $url ); 00273 $this->mRedirectCode = $responsecode; 00274 } 00275 00281 public function getRedirect() { 00282 return $this->mRedirect; 00283 } 00284 00290 public function setStatusCode( $statusCode ) { 00291 $this->mStatusCode = $statusCode; 00292 } 00293 00301 function addMeta( $name, $val ) { 00302 array_push( $this->mMetatags, array( $name, $val ) ); 00303 } 00304 00310 function addKeyword( $text ) { 00311 if( is_array( $text ) ) { 00312 $this->mKeywords = array_merge( $this->mKeywords, $text ); 00313 } else { 00314 array_push( $this->mKeywords, $text ); 00315 } 00316 } 00317 00323 function addLink( $linkarr ) { 00324 array_push( $this->mLinktags, $linkarr ); 00325 } 00326 00334 function addMetadataLink( $linkarr ) { 00335 $linkarr['rel'] = $this->getMetadataAttribute(); 00336 $this->addLink( $linkarr ); 00337 } 00338 00344 public function getMetadataAttribute() { 00345 # note: buggy CC software only reads first "meta" link 00346 static $haveMeta = false; 00347 if ( $haveMeta ) { 00348 return 'alternate meta'; 00349 } else { 00350 $haveMeta = true; 00351 return 'meta'; 00352 } 00353 } 00354 00360 function addScript( $script ) { 00361 $this->mScripts .= $script . "\n"; 00362 } 00363 00372 public function addExtensionStyle( $url ) { 00373 array_push( $this->mExtStyles, $url ); 00374 } 00375 00381 function getExtStyle() { 00382 return $this->mExtStyles; 00383 } 00384 00392 public function addScriptFile( $file, $version = null ) { 00393 global $wgStylePath, $wgStyleVersion; 00394 // See if $file parameter is an absolute URL or begins with a slash 00395 if( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) { 00396 $path = $file; 00397 } else { 00398 $path = "{$wgStylePath}/common/{$file}"; 00399 } 00400 if ( is_null( $version ) ) 00401 $version = $wgStyleVersion; 00402 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) ); 00403 } 00404 00410 public function addInlineScript( $script ) { 00411 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n"; 00412 } 00413 00419 function getScript() { 00420 return $this->mScripts . $this->getHeadItems(); 00421 } 00422 00431 protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ){ 00432 $resourceLoader = $this->getResourceLoader(); 00433 $filteredModules = array(); 00434 foreach( $modules as $val ){ 00435 $module = $resourceLoader->getModule( $val ); 00436 if( $module instanceof ResourceLoaderModule 00437 && $module->getOrigin() <= $this->getAllowedModules( $type ) 00438 && ( is_null( $position ) || $module->getPosition() == $position ) ) 00439 { 00440 $filteredModules[] = $val; 00441 } 00442 } 00443 return $filteredModules; 00444 } 00445 00454 public function getModules( $filter = false, $position = null, $param = 'mModules' ) { 00455 $modules = array_values( array_unique( $this->$param ) ); 00456 return $filter 00457 ? $this->filterModules( $modules, $position ) 00458 : $modules; 00459 } 00460 00468 public function addModules( $modules ) { 00469 $this->mModules = array_merge( $this->mModules, (array)$modules ); 00470 } 00471 00480 public function getModuleScripts( $filter = false, $position = null ) { 00481 return $this->getModules( $filter, $position, 'mModuleScripts' ); 00482 } 00483 00491 public function addModuleScripts( $modules ) { 00492 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules ); 00493 } 00494 00503 public function getModuleStyles( $filter = false, $position = null ) { 00504 return $this->getModules( $filter, $position, 'mModuleStyles' ); 00505 } 00506 00514 public function addModuleStyles( $modules ) { 00515 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules ); 00516 } 00517 00526 public function getModuleMessages( $filter = false, $position = null ) { 00527 return $this->getModules( $filter, $position, 'mModuleMessages' ); 00528 } 00529 00537 public function addModuleMessages( $modules ) { 00538 $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules ); 00539 } 00540 00546 function getHeadItemsArray() { 00547 return $this->mHeadItems; 00548 } 00549 00555 function getHeadItems() { 00556 $s = ''; 00557 foreach ( $this->mHeadItems as $item ) { 00558 $s .= $item; 00559 } 00560 return $s; 00561 } 00562 00569 public function addHeadItem( $name, $value ) { 00570 $this->mHeadItems[$name] = $value; 00571 } 00572 00579 public function hasHeadItem( $name ) { 00580 return isset( $this->mHeadItems[$name] ); 00581 } 00582 00588 function setETag( $tag ) { 00589 $this->mETag = $tag; 00590 } 00591 00599 public function setArticleBodyOnly( $only ) { 00600 $this->mArticleBodyOnly = $only; 00601 } 00602 00608 public function getArticleBodyOnly() { 00609 return $this->mArticleBodyOnly; 00610 } 00611 00623 public function checkLastModified( $timestamp ) { 00624 global $wgCachePages, $wgCacheEpoch; 00625 00626 if ( !$timestamp || $timestamp == '19700101000000' ) { 00627 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" ); 00628 return false; 00629 } 00630 if( !$wgCachePages ) { 00631 wfDebug( __METHOD__ . ": CACHE DISABLED\n", false ); 00632 return false; 00633 } 00634 if( $this->getUser()->getOption( 'nocache' ) ) { 00635 wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false ); 00636 return false; 00637 } 00638 00639 $timestamp = wfTimestamp( TS_MW, $timestamp ); 00640 $modifiedTimes = array( 00641 'page' => $timestamp, 00642 'user' => $this->getUser()->getTouched(), 00643 'epoch' => $wgCacheEpoch 00644 ); 00645 wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) ); 00646 00647 $maxModified = max( $modifiedTimes ); 00648 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified ); 00649 00650 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' ); 00651 if ( $clientHeader === false ) { 00652 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false ); 00653 return false; 00654 } 00655 00656 # IE sends sizes after the date like this: 00657 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 00658 # this breaks strtotime(). 00659 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader ); 00660 00661 wfSuppressWarnings(); // E_STRICT system time bitching 00662 $clientHeaderTime = strtotime( $clientHeader ); 00663 wfRestoreWarnings(); 00664 if ( !$clientHeaderTime ) { 00665 wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" ); 00666 return false; 00667 } 00668 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime ); 00669 00670 # Make debug info 00671 $info = ''; 00672 foreach ( $modifiedTimes as $name => $value ) { 00673 if ( $info !== '' ) { 00674 $info .= ', '; 00675 } 00676 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value ); 00677 } 00678 00679 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " . 00680 wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false ); 00681 wfDebug( __METHOD__ . ": effective Last-Modified: " . 00682 wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false ); 00683 if( $clientHeaderTime < $maxModified ) { 00684 wfDebug( __METHOD__ . ": STALE, $info\n", false ); 00685 return false; 00686 } 00687 00688 # Not modified 00689 # Give a 304 response code and disable body output 00690 wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false ); 00691 ini_set( 'zlib.output_compression', 0 ); 00692 $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" ); 00693 $this->sendCacheControl(); 00694 $this->disable(); 00695 00696 // Don't output a compressed blob when using ob_gzhandler; 00697 // it's technically against HTTP spec and seems to confuse 00698 // Firefox when the response gets split over two packets. 00699 wfClearOutputBuffers(); 00700 00701 return true; 00702 } 00703 00710 public function setLastModified( $timestamp ) { 00711 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp ); 00712 } 00713 00722 public function setRobotPolicy( $policy ) { 00723 $policy = Article::formatRobotPolicy( $policy ); 00724 00725 if( isset( $policy['index'] ) ) { 00726 $this->setIndexPolicy( $policy['index'] ); 00727 } 00728 if( isset( $policy['follow'] ) ) { 00729 $this->setFollowPolicy( $policy['follow'] ); 00730 } 00731 } 00732 00740 public function setIndexPolicy( $policy ) { 00741 $policy = trim( $policy ); 00742 if( in_array( $policy, array( 'index', 'noindex' ) ) ) { 00743 $this->mIndexPolicy = $policy; 00744 } 00745 } 00746 00754 public function setFollowPolicy( $policy ) { 00755 $policy = trim( $policy ); 00756 if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) { 00757 $this->mFollowPolicy = $policy; 00758 } 00759 } 00760 00767 public function setPageTitleActionText( $text ) { 00768 $this->mPageTitleActionText = $text; 00769 } 00770 00776 public function getPageTitleActionText() { 00777 if ( isset( $this->mPageTitleActionText ) ) { 00778 return $this->mPageTitleActionText; 00779 } 00780 } 00781 00788 public function setHTMLTitle( $name ) { 00789 if ( $name instanceof Message ) { 00790 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text(); 00791 } else { 00792 $this->mHTMLtitle = $name; 00793 } 00794 } 00795 00801 public function getHTMLTitle() { 00802 return $this->mHTMLtitle; 00803 } 00804 00810 public function setRedirectedFrom( $t ) { 00811 $this->mRedirectedFrom = $t; 00812 } 00813 00822 public function setPageTitle( $name ) { 00823 if ( $name instanceof Message ) { 00824 $name = $name->setContext( $this->getContext() )->text(); 00825 } 00826 00827 # change "<script>foo&bar</script>" to "<script>foo&bar</script>" 00828 # but leave "<i>foobar</i>" alone 00829 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) ); 00830 $this->mPagetitle = $nameWithTags; 00831 00832 # change "<i>foo&bar</i>" to "foo&bar" 00833 $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) ); 00834 } 00835 00841 public function getPageTitle() { 00842 return $this->mPagetitle; 00843 } 00844 00850 public function setTitle( Title $t ) { 00851 $this->getContext()->setTitle( $t ); 00852 } 00853 00854 00860 public function setSubtitle( $str ) { 00861 $this->clearSubtitle(); 00862 $this->addSubtitle( $str ); 00863 } 00864 00871 public function appendSubtitle( $str ) { 00872 $this->addSubtitle( $str ); 00873 } 00874 00880 public function addSubtitle( $str ) { 00881 if ( $str instanceof Message ) { 00882 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse(); 00883 } else { 00884 $this->mSubtitle[] = $str; 00885 } 00886 } 00887 00893 public function addBacklinkSubtitle( Title $title ) { 00894 $query = array(); 00895 if ( $title->isRedirect() ) { 00896 $query['redirect'] = 'no'; 00897 } 00898 $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) ); 00899 } 00900 00904 public function clearSubtitle() { 00905 $this->mSubtitle = array(); 00906 } 00907 00913 public function getSubtitle() { 00914 return implode( "<br />\n\t\t\t\t", $this->mSubtitle ); 00915 } 00916 00921 public function setPrintable() { 00922 $this->mPrintable = true; 00923 } 00924 00930 public function isPrintable() { 00931 return $this->mPrintable; 00932 } 00933 00937 public function disable() { 00938 $this->mDoNothing = true; 00939 } 00940 00946 public function isDisabled() { 00947 return $this->mDoNothing; 00948 } 00949 00955 public function showNewSectionLink() { 00956 return $this->mNewSectionLink; 00957 } 00958 00964 public function forceHideNewSectionLink() { 00965 return $this->mHideNewSectionLink; 00966 } 00967 00976 public function setSyndicated( $show = true ) { 00977 if ( $show ) { 00978 $this->setFeedAppendQuery( false ); 00979 } else { 00980 $this->mFeedLinks = array(); 00981 } 00982 } 00983 00993 public function setFeedAppendQuery( $val ) { 00994 global $wgAdvertisedFeedTypes; 00995 00996 $this->mFeedLinks = array(); 00997 00998 foreach ( $wgAdvertisedFeedTypes as $type ) { 00999 $query = "feed=$type"; 01000 if ( is_string( $val ) ) { 01001 $query .= '&' . $val; 01002 } 01003 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query ); 01004 } 01005 } 01006 01013 public function addFeedLink( $format, $href ) { 01014 global $wgAdvertisedFeedTypes; 01015 01016 if ( in_array( $format, $wgAdvertisedFeedTypes ) ) { 01017 $this->mFeedLinks[$format] = $href; 01018 } 01019 } 01020 01025 public function isSyndicated() { 01026 return count( $this->mFeedLinks ) > 0; 01027 } 01028 01033 public function getSyndicationLinks() { 01034 return $this->mFeedLinks; 01035 } 01036 01042 public function getFeedAppendQuery() { 01043 return $this->mFeedLinksAppendQuery; 01044 } 01045 01053 public function setArticleFlag( $v ) { 01054 $this->mIsarticle = $v; 01055 if ( $v ) { 01056 $this->mIsArticleRelated = $v; 01057 } 01058 } 01059 01066 public function isArticle() { 01067 return $this->mIsarticle; 01068 } 01069 01076 public function setArticleRelated( $v ) { 01077 $this->mIsArticleRelated = $v; 01078 if ( !$v ) { 01079 $this->mIsarticle = false; 01080 } 01081 } 01082 01088 public function isArticleRelated() { 01089 return $this->mIsArticleRelated; 01090 } 01091 01098 public function addLanguageLinks( $newLinkArray ) { 01099 $this->mLanguageLinks += $newLinkArray; 01100 } 01101 01108 public function setLanguageLinks( $newLinkArray ) { 01109 $this->mLanguageLinks = $newLinkArray; 01110 } 01111 01117 public function getLanguageLinks() { 01118 return $this->mLanguageLinks; 01119 } 01120 01126 public function addCategoryLinks( $categories ) { 01127 global $wgContLang; 01128 01129 if ( !is_array( $categories ) || count( $categories ) == 0 ) { 01130 return; 01131 } 01132 01133 # Add the links to a LinkBatch 01134 $arr = array( NS_CATEGORY => $categories ); 01135 $lb = new LinkBatch; 01136 $lb->setArray( $arr ); 01137 01138 # Fetch existence plus the hiddencat property 01139 $dbr = wfGetDB( DB_SLAVE ); 01140 $res = $dbr->select( array( 'page', 'page_props' ), 01141 array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ), 01142 $lb->constructSet( 'page', $dbr ), 01143 __METHOD__, 01144 array(), 01145 array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) ) 01146 ); 01147 01148 # Add the results to the link cache 01149 $lb->addResultToCache( LinkCache::singleton(), $res ); 01150 01151 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+ 01152 $categories = array_combine( 01153 array_keys( $categories ), 01154 array_fill( 0, count( $categories ), 'normal' ) 01155 ); 01156 01157 # Mark hidden categories 01158 foreach ( $res as $row ) { 01159 if ( isset( $row->pp_value ) ) { 01160 $categories[$row->page_title] = 'hidden'; 01161 } 01162 } 01163 01164 # Add the remaining categories to the skin 01165 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) { 01166 foreach ( $categories as $category => $type ) { 01167 $origcategory = $category; 01168 $title = Title::makeTitleSafe( NS_CATEGORY, $category ); 01169 $wgContLang->findVariantLink( $category, $title, true ); 01170 if ( $category != $origcategory ) { 01171 if ( array_key_exists( $category, $categories ) ) { 01172 continue; 01173 } 01174 } 01175 $text = $wgContLang->convertHtml( $title->getText() ); 01176 $this->mCategories[] = $title->getText(); 01177 $this->mCategoryLinks[$type][] = Linker::link( $title, $text ); 01178 } 01179 } 01180 } 01181 01187 public function setCategoryLinks( $categories ) { 01188 $this->mCategoryLinks = array(); 01189 $this->addCategoryLinks( $categories ); 01190 } 01191 01200 public function getCategoryLinks() { 01201 return $this->mCategoryLinks; 01202 } 01203 01209 public function getCategories() { 01210 return $this->mCategories; 01211 } 01212 01217 public function disallowUserJs() { 01218 $this->reduceAllowedModules( 01219 ResourceLoaderModule::TYPE_SCRIPTS, 01220 ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL 01221 ); 01222 } 01223 01231 public function isUserJsAllowed() { 01232 wfDeprecated( __METHOD__, '1.18' ); 01233 return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL; 01234 } 01235 01242 public function getAllowedModules( $type ){ 01243 if( $type == ResourceLoaderModule::TYPE_COMBINED ){ 01244 return min( array_values( $this->mAllowedModules ) ); 01245 } else { 01246 return isset( $this->mAllowedModules[$type] ) 01247 ? $this->mAllowedModules[$type] 01248 : ResourceLoaderModule::ORIGIN_ALL; 01249 } 01250 } 01251 01257 public function setAllowedModules( $type, $level ){ 01258 $this->mAllowedModules[$type] = $level; 01259 } 01260 01266 public function reduceAllowedModules( $type, $level ){ 01267 $this->mAllowedModules[$type] = min( $this->getAllowedModules($type), $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 01324 public function parserOptions( $options = null ) { 01325 if ( !$this->mParserOptions ) { 01326 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() ); 01327 $this->mParserOptions->setEditSection( false ); 01328 } 01329 return wfSetVar( $this->mParserOptions, $options ); 01330 } 01331 01339 public function setRevisionId( $revid ) { 01340 $val = is_null( $revid ) ? null : intval( $revid ); 01341 return wfSetVar( $this->mRevisionId, $val ); 01342 } 01343 01349 public function getRevisionId() { 01350 return $this->mRevisionId; 01351 } 01352 01360 public function setRevisionTimestamp( $timestamp) { 01361 return wfSetVar( $this->mRevisionTimestamp, $timestamp ); 01362 } 01363 01370 public function getRevisionTimestamp() { 01371 return $this->mRevisionTimestamp; 01372 } 01373 01380 public function setFileVersion( $file ) { 01381 $val = null; 01382 if ( $file instanceof File && $file->exists() ) { 01383 $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ); 01384 } 01385 return wfSetVar( $this->mFileVersion, $val, true ); 01386 } 01387 01393 public function getFileVersion() { 01394 return $this->mFileVersion; 01395 } 01396 01403 public function getTemplateIds() { 01404 return $this->mTemplateIds; 01405 } 01406 01413 public function getFileSearchOptions() { 01414 return $this->mImageTimeKeys; 01415 } 01416 01425 public function addWikiText( $text, $linestart = true, $interface = true ) { 01426 $title = $this->getTitle(); // Work arround E_STRICT 01427 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface ); 01428 } 01429 01437 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) { 01438 $this->addWikiTextTitle( $text, $title, $linestart ); 01439 } 01440 01448 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) { 01449 $this->addWikiTextTitle( $text, $title, $linestart, true ); 01450 } 01451 01458 public function addWikiTextTidy( $text, $linestart = true ) { 01459 $title = $this->getTitle(); 01460 $this->addWikiTextTitleTidy( $text, $title, $linestart ); 01461 } 01462 01473 public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false, $interface = false ) { 01474 global $wgParser; 01475 01476 wfProfileIn( __METHOD__ ); 01477 01478 $popts = $this->parserOptions(); 01479 $oldTidy = $popts->setTidy( $tidy ); 01480 $popts->setInterfaceMessage( (bool) $interface ); 01481 01482 $parserOutput = $wgParser->parse( 01483 $text, $title, $popts, 01484 $linestart, true, $this->mRevisionId 01485 ); 01486 01487 $popts->setTidy( $oldTidy ); 01488 01489 $this->addParserOutput( $parserOutput ); 01490 01491 wfProfileOut( __METHOD__ ); 01492 } 01493 01499 public function addParserOutputNoText( &$parserOutput ) { 01500 $this->mLanguageLinks += $parserOutput->getLanguageLinks(); 01501 $this->addCategoryLinks( $parserOutput->getCategories() ); 01502 $this->mNewSectionLink = $parserOutput->getNewSection(); 01503 $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); 01504 01505 $this->mParseWarnings = $parserOutput->getWarnings(); 01506 if ( !$parserOutput->isCacheable() ) { 01507 $this->enableClientCache( false ); 01508 } 01509 $this->mNoGallery = $parserOutput->getNoGallery(); 01510 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() ); 01511 $this->addModules( $parserOutput->getModules() ); 01512 $this->addModuleScripts( $parserOutput->getModuleScripts() ); 01513 $this->addModuleStyles( $parserOutput->getModuleStyles() ); 01514 $this->addModuleMessages( $parserOutput->getModuleMessages() ); 01515 01516 // Template versioning... 01517 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) { 01518 if ( isset( $this->mTemplateIds[$ns] ) ) { 01519 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns]; 01520 } else { 01521 $this->mTemplateIds[$ns] = $dbks; 01522 } 01523 } 01524 // File versioning... 01525 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) { 01526 $this->mImageTimeKeys[$dbk] = $data; 01527 } 01528 01529 // Hooks registered in the object 01530 global $wgParserOutputHooks; 01531 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) { 01532 list( $hookName, $data ) = $hookInfo; 01533 if ( isset( $wgParserOutputHooks[$hookName] ) ) { 01534 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data ); 01535 } 01536 } 01537 01538 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); 01539 } 01540 01546 function addParserOutput( &$parserOutput ) { 01547 $this->addParserOutputNoText( $parserOutput ); 01548 $text = $parserOutput->getText(); 01549 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) ); 01550 $this->addHTML( $text ); 01551 } 01552 01553 01559 public function addTemplate( &$template ) { 01560 ob_start(); 01561 $template->execute(); 01562 $this->addHTML( ob_get_contents() ); 01563 ob_end_clean(); 01564 } 01565 01579 public function parse( $text, $linestart = true, $interface = false, $language = null ) { 01580 global $wgParser; 01581 01582 if( is_null( $this->getTitle() ) ) { 01583 throw new MWException( 'Empty $mTitle in ' . __METHOD__ ); 01584 } 01585 01586 $popts = $this->parserOptions(); 01587 if ( $interface ) { 01588 $popts->setInterfaceMessage( true ); 01589 } 01590 if ( $language !== null ) { 01591 $oldLang = $popts->setTargetLanguage( $language ); 01592 } 01593 01594 $parserOutput = $wgParser->parse( 01595 $text, $this->getTitle(), $popts, 01596 $linestart, true, $this->mRevisionId 01597 ); 01598 01599 if ( $interface ) { 01600 $popts->setInterfaceMessage( false ); 01601 } 01602 if ( $language !== null ) { 01603 $popts->setTargetLanguage( $oldLang ); 01604 } 01605 01606 return $parserOutput->getText(); 01607 } 01608 01619 public function parseInline( $text, $linestart = true, $interface = false ) { 01620 $parsed = $this->parse( $text, $linestart, $interface ); 01621 01622 $m = array(); 01623 if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) { 01624 $parsed = $m[1]; 01625 } 01626 01627 return $parsed; 01628 } 01629 01635 public function setSquidMaxage( $maxage ) { 01636 $this->mSquidMaxage = $maxage; 01637 } 01638 01646 public function enableClientCache( $state ) { 01647 return wfSetVar( $this->mEnableClientCache, $state ); 01648 } 01649 01655 function getCacheVaryCookies() { 01656 global $wgCookiePrefix, $wgCacheVaryCookies; 01657 static $cookies; 01658 if ( $cookies === null ) { 01659 $cookies = array_merge( 01660 array( 01661 "{$wgCookiePrefix}Token", 01662 "{$wgCookiePrefix}LoggedOut", 01663 session_name() 01664 ), 01665 $wgCacheVaryCookies 01666 ); 01667 wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) ); 01668 } 01669 return $cookies; 01670 } 01671 01678 function haveCacheVaryCookies() { 01679 $cookieHeader = $this->getRequest()->getHeader( 'cookie' ); 01680 if ( $cookieHeader === false ) { 01681 return false; 01682 } 01683 $cvCookies = $this->getCacheVaryCookies(); 01684 foreach ( $cvCookies as $cookieName ) { 01685 # Check for a simple string match, like the way squid does it 01686 if ( strpos( $cookieHeader, $cookieName ) !== false ) { 01687 wfDebug( __METHOD__ . ": found $cookieName\n" ); 01688 return true; 01689 } 01690 } 01691 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" ); 01692 return false; 01693 } 01694 01703 public function addVaryHeader( $header, $option = null ) { 01704 if ( !array_key_exists( $header, $this->mVaryHeader ) ) { 01705 $this->mVaryHeader[$header] = (array)$option; 01706 } elseif( is_array( $option ) ) { 01707 if( is_array( $this->mVaryHeader[$header] ) ) { 01708 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option ); 01709 } else { 01710 $this->mVaryHeader[$header] = $option; 01711 } 01712 } 01713 $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] ); 01714 } 01715 01722 public function getVaryHeader() { 01723 return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ); 01724 } 01725 01731 public function getXVO() { 01732 $cvCookies = $this->getCacheVaryCookies(); 01733 01734 $cookiesOption = array(); 01735 foreach ( $cvCookies as $cookieName ) { 01736 $cookiesOption[] = 'string-contains=' . $cookieName; 01737 } 01738 $this->addVaryHeader( 'Cookie', $cookiesOption ); 01739 01740 $headers = array(); 01741 foreach( $this->mVaryHeader as $header => $option ) { 01742 $newheader = $header; 01743 if ( is_array( $option ) && count( $option ) > 0 ) { 01744 $newheader .= ';' . implode( ';', $option ); 01745 } 01746 $headers[] = $newheader; 01747 } 01748 $xvo = 'X-Vary-Options: ' . implode( ',', $headers ); 01749 01750 return $xvo; 01751 } 01752 01761 function addAcceptLanguage() { 01762 $lang = $this->getTitle()->getPageLanguage(); 01763 if( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) { 01764 $variants = $lang->getVariants(); 01765 $aloption = array(); 01766 foreach ( $variants as $variant ) { 01767 if( $variant === $lang->getCode() ) { 01768 continue; 01769 } else { 01770 $aloption[] = 'string-contains=' . $variant; 01771 01772 // IE and some other browsers use another form of language code 01773 // in their Accept-Language header, like "zh-CN" or "zh-TW". 01774 // We should handle these too. 01775 $ievariant = explode( '-', $variant ); 01776 if ( count( $ievariant ) == 2 ) { 01777 $ievariant[1] = strtoupper( $ievariant[1] ); 01778 $ievariant = implode( '-', $ievariant ); 01779 $aloption[] = 'string-contains=' . $ievariant; 01780 } 01781 } 01782 } 01783 $this->addVaryHeader( 'Accept-Language', $aloption ); 01784 } 01785 } 01786 01797 public function preventClickjacking( $enable = true ) { 01798 $this->mPreventClickjacking = $enable; 01799 } 01800 01806 public function allowClickjacking() { 01807 $this->mPreventClickjacking = false; 01808 } 01809 01817 public function getFrameOptions() { 01818 global $wgBreakFrames, $wgEditPageFrameOptions; 01819 if ( $wgBreakFrames ) { 01820 return 'DENY'; 01821 } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) { 01822 return $wgEditPageFrameOptions; 01823 } 01824 return false; 01825 } 01826 01830 public function sendCacheControl() { 01831 global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO; 01832 01833 $response = $this->getRequest()->response(); 01834 if ( $wgUseETag && $this->mETag ) { 01835 $response->header( "ETag: $this->mETag" ); 01836 } 01837 01838 $this->addVaryHeader( 'Cookie' ); 01839 $this->addAcceptLanguage(); 01840 01841 # don't serve compressed data to clients who can't handle it 01842 # maintain different caches for logged-in users and non-logged in ones 01843 $response->header( $this->getVaryHeader() ); 01844 01845 if ( $wgUseXVO ) { 01846 # Add an X-Vary-Options header for Squid with Wikimedia patches 01847 $response->header( $this->getXVO() ); 01848 } 01849 01850 if( $this->mEnableClientCache ) { 01851 if( 01852 $wgUseSquid && session_id() == '' && !$this->isPrintable() && 01853 $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() 01854 ) 01855 { 01856 if ( $wgUseESI ) { 01857 # We'll purge the proxy cache explicitly, but require end user agents 01858 # to revalidate against the proxy on each visit. 01859 # Surrogate-Control controls our Squid, Cache-Control downstream caches 01860 wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false ); 01861 # start with a shorter timeout for initial testing 01862 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"'); 01863 $response->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"'); 01864 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); 01865 } else { 01866 # We'll purge the proxy cache for anons explicitly, but require end user agents 01867 # to revalidate against the proxy on each visit. 01868 # IMPORTANT! The Squid needs to replace the Cache-Control header with 01869 # Cache-Control: s-maxage=0, must-revalidate, max-age=0 01870 wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false ); 01871 # start with a shorter timeout for initial testing 01872 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" ); 01873 $response->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' ); 01874 } 01875 } else { 01876 # We do want clients to cache if they can, but they *must* check for updates 01877 # on revisiting the page. 01878 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false ); 01879 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01880 $response->header( "Cache-Control: private, must-revalidate, max-age=0" ); 01881 } 01882 if($this->mLastModified) { 01883 $response->header( "Last-Modified: {$this->mLastModified}" ); 01884 } 01885 } else { 01886 wfDebug( __METHOD__ . ": no caching **\n", false ); 01887 01888 # In general, the absence of a last modified header should be enough to prevent 01889 # the client from using its cache. We send a few other things just to make sure. 01890 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01891 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); 01892 $response->header( 'Pragma: no-cache' ); 01893 } 01894 } 01895 01905 public static function getStatusMessage( $code ) { 01906 wfDeprecated( __METHOD__ ); 01907 return HttpStatus::getMessage( $code ); 01908 } 01909 01914 public function output() { 01915 global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP; 01916 01917 if( $this->mDoNothing ) { 01918 return; 01919 } 01920 01921 wfProfileIn( __METHOD__ ); 01922 01923 $response = $this->getRequest()->response(); 01924 01925 if ( $this->mRedirect != '' ) { 01926 # Standards require redirect URLs to be absolute 01927 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT ); 01928 01929 $redirect = $this->mRedirect; 01930 $code = $this->mRedirectCode; 01931 01932 if( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) { 01933 if( $code == '301' || $code == '303' ) { 01934 if( !$wgDebugRedirects ) { 01935 $message = HttpStatus::getMessage( $code ); 01936 $response->header( "HTTP/1.1 $code $message" ); 01937 } 01938 $this->mLastModified = wfTimestamp( TS_RFC2822 ); 01939 } 01940 if ( $wgVaryOnXFP ) { 01941 $this->addVaryHeader( 'X-Forwarded-Proto' ); 01942 } 01943 $this->sendCacheControl(); 01944 01945 $response->header( "Content-Type: text/html; charset=utf-8" ); 01946 if( $wgDebugRedirects ) { 01947 $url = htmlspecialchars( $redirect ); 01948 print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n"; 01949 print "<p>Location: <a href=\"$url\">$url</a></p>\n"; 01950 print "</body>\n</html>\n"; 01951 } else { 01952 $response->header( 'Location: ' . $redirect ); 01953 } 01954 } 01955 01956 wfProfileOut( __METHOD__ ); 01957 return; 01958 } elseif ( $this->mStatusCode ) { 01959 $message = HttpStatus::getMessage( $this->mStatusCode ); 01960 if ( $message ) { 01961 $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message ); 01962 } 01963 } 01964 01965 # Buffer output; final headers may depend on later processing 01966 ob_start(); 01967 01968 $response->header( "Content-type: $wgMimeType; charset=UTF-8" ); 01969 $response->header( 'Content-language: ' . $wgLanguageCode ); 01970 01971 // Prevent framing, if requested 01972 $frameOptions = $this->getFrameOptions(); 01973 if ( $frameOptions ) { 01974 $response->header( "X-Frame-Options: $frameOptions" ); 01975 } 01976 01977 if ( $this->mArticleBodyOnly ) { 01978 $this->out( $this->mBodytext ); 01979 } else { 01980 $this->addDefaultModules(); 01981 01982 $sk = $this->getSkin(); 01983 01984 // Hook that allows last minute changes to the output page, e.g. 01985 // adding of CSS or Javascript by extensions. 01986 wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) ); 01987 01988 wfProfileIn( 'Output-skin' ); 01989 $sk->outputPage(); 01990 wfProfileOut( 'Output-skin' ); 01991 } 01992 01993 // This hook allows last minute changes to final overall output by modifying output buffer 01994 wfRunHooks( 'AfterFinalPageOutput', array( $this ) ); 01995 01996 $this->sendCacheControl(); 01997 ob_end_flush(); 01998 wfProfileOut( __METHOD__ ); 01999 } 02000 02006 public function out( $ins ) { 02007 print $ins; 02008 } 02009 02014 function blockedPage() { 02015 throw new UserBlockedError( $this->getUser()->mBlock ); 02016 } 02017 02028 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) { 02029 $this->setPageTitle( $pageTitle ); 02030 if ( $htmlTitle !== false ) { 02031 $this->setHTMLTitle( $htmlTitle ); 02032 } 02033 $this->setRobotPolicy( 'noindex,nofollow' ); 02034 $this->setArticleRelated( false ); 02035 $this->enableClientCache( false ); 02036 $this->mRedirect = ''; 02037 $this->clearSubtitle(); 02038 $this->clearHTML(); 02039 } 02040 02052 public function showErrorPage( $title, $msg, $params = array() ) { 02053 if( !$title instanceof Message ) { 02054 $title = $this->msg( $title ); 02055 } 02056 02057 $this->prepareErrorPage( $title ); 02058 02059 if ( $msg instanceof Message ){ 02060 $this->addHTML( $msg->parse() ); 02061 } else { 02062 $this->addWikiMsgArray( $msg, $params ); 02063 } 02064 02065 $this->returnToMain(); 02066 } 02067 02074 public function showPermissionsErrorPage( $errors, $action = null ) { 02075 global $wgGroupPermissions; 02076 02077 // For some action (read, edit, create and upload), display a "login to do this action" 02078 // error if all of the following conditions are met: 02079 // 1. the user is not logged in 02080 // 2. the only error is insufficient permissions (i.e. no block or something else) 02081 // 3. the error can be avoided simply by logging in 02082 if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) ) 02083 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] ) 02084 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' ) 02085 && ( ( isset( $wgGroupPermissions['user'][$action] ) && $wgGroupPermissions['user'][$action] ) 02086 || ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['autoconfirmed'][$action] ) ) 02087 ) { 02088 $displayReturnto = null; 02089 02090 # Due to bug 32276, if a user does not have read permissions, 02091 # $this->getTitle() will just give Special:Badtitle, which is 02092 # not especially useful as a returnto parameter. Use the title 02093 # from the request instead, if there was one. 02094 $request = $this->getRequest(); 02095 $returnto = Title::newFromURL( $request->getVal( 'title', '' ) ); 02096 if ( $action == 'edit' ) { 02097 $msg = 'whitelistedittext'; 02098 $displayReturnto = $returnto; 02099 } elseif ( $action == 'createpage' || $action == 'createtalk' ) { 02100 $msg = 'nocreatetext'; 02101 } elseif ( $action == 'upload' ) { 02102 $msg = 'uploadnologintext'; 02103 } else { # Read 02104 $msg = 'loginreqpagetext'; 02105 $displayReturnto = Title::newMainPage(); 02106 } 02107 02108 $query = array(); 02109 02110 if ( $returnto ) { 02111 $query['returnto'] = $returnto->getPrefixedText(); 02112 02113 if ( !$request->wasPosted() ) { 02114 $returntoquery = $request->getValues(); 02115 unset( $returntoquery['title'] ); 02116 unset( $returntoquery['returnto'] ); 02117 unset( $returntoquery['returntoquery'] ); 02118 $query['returntoquery'] = wfArrayToCGI( $returntoquery ); 02119 } 02120 } 02121 $loginLink = Linker::linkKnown( 02122 SpecialPage::getTitleFor( 'Userlogin' ), 02123 $this->msg( 'loginreqlink' )->escaped(), 02124 array(), 02125 $query 02126 ); 02127 02128 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) ); 02129 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() ); 02130 02131 # Don't return to a page the user can't read otherwise 02132 # we'll end up in a pointless loop 02133 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) { 02134 $this->returnToMain( null, $displayReturnto ); 02135 } 02136 } else { 02137 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) ); 02138 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) ); 02139 } 02140 } 02141 02148 public function versionRequired( $version ) { 02149 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) ); 02150 02151 $this->addWikiMsg( 'versionrequiredtext', $version ); 02152 $this->returnToMain(); 02153 } 02154 02160 public function permissionRequired( $permission ) { 02161 throw new PermissionsError( $permission ); 02162 } 02163 02169 public function loginToUse() { 02170 throw new PermissionsError( 'read' ); 02171 } 02172 02180 public function formatPermissionsErrorMessage( $errors, $action = null ) { 02181 if ( $action == null ) { 02182 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n"; 02183 } else { 02184 $action_desc = $this->msg( "action-$action" )->plain(); 02185 $text = $this->msg( 02186 'permissionserrorstext-withaction', 02187 count( $errors ), 02188 $action_desc 02189 )->plain() . "\n\n"; 02190 } 02191 02192 if ( count( $errors ) > 1 ) { 02193 $text .= '<ul class="permissions-errors">' . "\n"; 02194 02195 foreach( $errors as $error ) { 02196 $text .= '<li>'; 02197 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain(); 02198 $text .= "</li>\n"; 02199 } 02200 $text .= '</ul>'; 02201 } else { 02202 $text .= "<div class=\"permissions-errors\">\n" . 02203 call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() . 02204 "\n</div>"; 02205 } 02206 02207 return $text; 02208 } 02209 02230 public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) { 02231 $this->setRobotPolicy( 'noindex,nofollow' ); 02232 $this->setArticleRelated( false ); 02233 02234 // If no reason is given, just supply a default "I can't let you do 02235 // that, Dave" message. Should only occur if called by legacy code. 02236 if ( $protected && empty( $reasons ) ) { 02237 $reasons[] = array( 'badaccess-group0' ); 02238 } 02239 02240 if ( !empty( $reasons ) ) { 02241 // Permissions error 02242 if( $source ) { 02243 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) ); 02244 $this->addBacklinkSubtitle( $this->getTitle() ); 02245 } else { 02246 $this->setPageTitle( $this->msg( 'badaccess' ) ); 02247 } 02248 $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) ); 02249 } else { 02250 // Wiki is read only 02251 throw new ReadOnlyError; 02252 } 02253 02254 // Show source, if supplied 02255 if( is_string( $source ) ) { 02256 $this->addWikiMsg( 'viewsourcetext' ); 02257 02258 $pageLang = $this->getTitle()->getPageLanguage(); 02259 $params = array( 02260 'id' => 'wpTextbox1', 02261 'name' => 'wpTextbox1', 02262 'cols' => $this->getUser()->getOption( 'cols' ), 02263 'rows' => $this->getUser()->getOption( 'rows' ), 02264 'readonly' => 'readonly', 02265 'lang' => $pageLang->getHtmlCode(), 02266 'dir' => $pageLang->getDir(), 02267 ); 02268 $this->addHTML( Html::element( 'textarea', $params, $source ) ); 02269 02270 // Show templates used by this article 02271 $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() ); 02272 $this->addHTML( "<div class='templatesUsed'> 02273 $templates 02274 </div> 02275 " ); 02276 } 02277 02278 # If the title doesn't exist, it's fairly pointless to print a return 02279 # link to it. After all, you just tried editing it and couldn't, so 02280 # what's there to do there? 02281 if( $this->getTitle()->exists() ) { 02282 $this->returnToMain( null, $this->getTitle() ); 02283 } 02284 } 02285 02290 public function rateLimited() { 02291 throw new ThrottledError; 02292 } 02293 02303 public function showLagWarning( $lag ) { 02304 global $wgSlaveLagWarning, $wgSlaveLagCritical; 02305 if( $lag >= $wgSlaveLagWarning ) { 02306 $message = $lag < $wgSlaveLagCritical 02307 ? 'lag-warn-normal' 02308 : 'lag-warn-high'; 02309 $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" ); 02310 $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) ); 02311 } 02312 } 02313 02314 public function showFatalError( $message ) { 02315 $this->prepareErrorPage( $this->msg( 'internalerror' ) ); 02316 02317 $this->addHTML( $message ); 02318 } 02319 02320 public function showUnexpectedValueError( $name, $val ) { 02321 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() ); 02322 } 02323 02324 public function showFileCopyError( $old, $new ) { 02325 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() ); 02326 } 02327 02328 public function showFileRenameError( $old, $new ) { 02329 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() ); 02330 } 02331 02332 public function showFileDeleteError( $name ) { 02333 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() ); 02334 } 02335 02336 public function showFileNotFoundError( $name ) { 02337 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() ); 02338 } 02339 02347 public function addReturnTo( $title, $query = array(), $text = null ) { 02348 $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL() ) ); 02349 $link = $this->msg( 'returnto' )->rawParams( 02350 Linker::link( $title, $text, array(), $query ) )->escaped(); 02351 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" ); 02352 } 02353 02362 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) { 02363 if ( $returnto == null ) { 02364 $returnto = $this->getRequest()->getText( 'returnto' ); 02365 } 02366 02367 if ( $returntoquery == null ) { 02368 $returntoquery = $this->getRequest()->getText( 'returntoquery' ); 02369 } 02370 02371 if ( $returnto === '' ) { 02372 $returnto = Title::newMainPage(); 02373 } 02374 02375 if ( is_object( $returnto ) ) { 02376 $titleObj = $returnto; 02377 } else { 02378 $titleObj = Title::newFromText( $returnto ); 02379 } 02380 if ( !is_object( $titleObj ) ) { 02381 $titleObj = Title::newMainPage(); 02382 } 02383 02384 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) ); 02385 } 02386 02392 public function headElement( Skin $sk, $includeStyle = true ) { 02393 global $wgContLang; 02394 02395 $userdir = $this->getLanguage()->getDir(); 02396 $sitedir = $wgContLang->getDir(); 02397 02398 if ( $sk->commonPrintStylesheet() ) { 02399 $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' ); 02400 } 02401 02402 $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) ); 02403 02404 if ( $this->getHTMLTitle() == '' ) { 02405 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) ); 02406 } 02407 02408 $openHead = Html::openElement( 'head' ); 02409 if ( $openHead ) { 02410 # Don't bother with the newline if $head == '' 02411 $ret .= "$openHead\n"; 02412 } 02413 02414 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n"; 02415 02416 $ret .= implode( "\n", array( 02417 $this->getHeadLinks( null, true ), 02418 $this->buildCssLinks(), 02419 $this->getHeadScripts(), 02420 $this->getHeadItems() 02421 ) ); 02422 02423 $closeHead = Html::closeElement( 'head' ); 02424 if ( $closeHead ) { 02425 $ret .= "$closeHead\n"; 02426 } 02427 02428 $bodyAttrs = array(); 02429 02430 # Classes for LTR/RTL directionality support 02431 $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir"; 02432 02433 if ( $this->getLanguage()->capitalizeAllNouns() ) { 02434 # A <body> class is probably not the best way to do this . . . 02435 $bodyAttrs['class'] .= ' capitalize-all-nouns'; 02436 } 02437 $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() ); 02438 $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() ); 02439 $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) ); 02440 02441 $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need 02442 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) ); 02443 02444 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n"; 02445 02446 return $ret; 02447 } 02448 02452 private function addDefaultModules() { 02453 global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax, 02454 $wgAjaxWatch; 02455 02456 // Add base resources 02457 $this->addModules( array( 02458 'mediawiki.user', 02459 'mediawiki.page.startup', 02460 'mediawiki.page.ready', 02461 ) ); 02462 if ( $wgIncludeLegacyJavaScript ){ 02463 $this->addModules( 'mediawiki.legacy.wikibits' ); 02464 } 02465 02466 if ( $wgPreloadJavaScriptMwUtil ) { 02467 $this->addModules( 'mediawiki.util' ); 02468 } 02469 02470 MWDebug::addModules( $this ); 02471 02472 // Add various resources if required 02473 if ( $wgUseAjax ) { 02474 $this->addModules( 'mediawiki.legacy.ajax' ); 02475 02476 wfRunHooks( 'AjaxAddScript', array( &$this ) ); 02477 02478 if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) { 02479 $this->addModules( 'mediawiki.page.watch.ajax' ); 02480 } 02481 02482 if ( !$this->getUser()->getOption( 'disablesuggest', false ) ) { 02483 $this->addModules( 'mediawiki.searchSuggest' ); 02484 } 02485 } 02486 02487 if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) { 02488 $this->addModules( 'mediawiki.action.view.rightClickEdit' ); 02489 } 02490 02491 # Crazy edit-on-double-click stuff 02492 if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) { 02493 $this->addModules( 'mediawiki.action.view.dblClickEdit' ); 02494 } 02495 } 02496 02502 public function getResourceLoader() { 02503 if ( is_null( $this->mResourceLoader ) ) { 02504 $this->mResourceLoader = new ResourceLoader(); 02505 } 02506 return $this->mResourceLoader; 02507 } 02508 02518 protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) { 02519 global $wgResourceLoaderUseESI; 02520 02521 $modules = (array) $modules; 02522 02523 if ( !count( $modules ) ) { 02524 return ''; 02525 } 02526 02527 if ( count( $modules ) > 1 ) { 02528 // Remove duplicate module requests 02529 $modules = array_unique( $modules ); 02530 // Sort module names so requests are more uniform 02531 sort( $modules ); 02532 02533 if ( ResourceLoader::inDebugMode() ) { 02534 // Recursively call us for every item 02535 $links = ''; 02536 foreach ( $modules as $name ) { 02537 $links .= $this->makeResourceLoaderLink( $name, $only, $useESI ); 02538 } 02539 return $links; 02540 } 02541 } 02542 02543 // Create keyed-by-group list of module objects from modules list 02544 $groups = array(); 02545 $resourceLoader = $this->getResourceLoader(); 02546 foreach ( $modules as $name ) { 02547 $module = $resourceLoader->getModule( $name ); 02548 # Check that we're allowed to include this module on this page 02549 if ( !$module 02550 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) 02551 && $only == ResourceLoaderModule::TYPE_SCRIPTS ) 02552 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES ) 02553 && $only == ResourceLoaderModule::TYPE_STYLES ) 02554 ) 02555 { 02556 continue; 02557 } 02558 02559 $group = $module->getGroup(); 02560 if ( !isset( $groups[$group] ) ) { 02561 $groups[$group] = array(); 02562 } 02563 $groups[$group][$name] = $module; 02564 } 02565 02566 $links = ''; 02567 foreach ( $groups as $group => $grpModules ) { 02568 // Special handling for user-specific groups 02569 $user = null; 02570 if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) { 02571 $user = $this->getUser()->getName(); 02572 } 02573 02574 // Create a fake request based on the one we are about to make so modules return 02575 // correct timestamp and emptiness data 02576 $query = ResourceLoader::makeLoaderQuery( 02577 array(), // modules; not determined yet 02578 $this->getLanguage()->getCode(), 02579 $this->getSkin()->getSkinName(), 02580 $user, 02581 null, // version; not determined yet 02582 ResourceLoader::inDebugMode(), 02583 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02584 $this->isPrintable(), 02585 $this->getRequest()->getBool( 'handheld' ), 02586 $extraQuery 02587 ); 02588 $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); 02589 // Extract modules that know they're empty 02590 $emptyModules = array (); 02591 foreach ( $grpModules as $key => $module ) { 02592 if ( $module->isKnownEmpty( $context ) ) { 02593 $emptyModules[$key] = 'ready'; 02594 unset( $grpModules[$key] ); 02595 } 02596 } 02597 // Inline empty modules: since they're empty, just mark them as 'ready' 02598 if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) { 02599 // If we're only getting the styles, we don't need to do anything for empty modules. 02600 $links .= Html::inlineScript( 02601 02602 ResourceLoader::makeLoaderConditionalScript( 02603 02604 ResourceLoader::makeLoaderStateScript( $emptyModules ) 02605 02606 ) 02607 02608 ) . "\n"; 02609 } 02610 02611 // If there are no modules left, skip this group 02612 if ( count( $grpModules ) === 0 ) { 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, $grpModules ) 02624 ); 02625 } else { 02626 $links .= Html::inlineScript( 02627 ResourceLoader::makeLoaderConditionalScript( 02628 $resourceLoader->makeModuleResponse( $context, $grpModules ) 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 ( $grpModules 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( $grpModules ), 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 $defaultModules = 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 $defaultModules['site'] = 'loading'; 02788 } else { 02789 // The wiki is configured to not allow a site module. 02790 $defaultModules['site'] = 'missing'; 02791 } 02792 02793 // Add user JS if enabled 02794 if ( $wgAllowUserJs ) { 02795 if ( $this->getUser()->isLoggedIn() ) { 02796 if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) { 02797 # XXX: additional security check/prompt? 02798 // We're on a preview of a JS subpage 02799 // Exclude this page from the user module in case it's in there (bug 26283) 02800 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false, 02801 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead 02802 ); 02803 // Load the previewed JS 02804 $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n"; 02805 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded 02806 // asynchronously and may arrive *after* the inline script here. So the previewed code 02807 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js... 02808 } else { 02809 // Include the user module normally, i.e., raw to avoid it being wrapped in a closure. 02810 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, 02811 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02812 ); 02813 } 02814 $defaultModules['user'] = 'loading'; 02815 } else { 02816 // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid 02817 // blocking default gadgets that might depend on it. Although arguably default-enabled 02818 // gadgets should not depend on the user module, it's harmless and less error-prone to 02819 // handle this case. 02820 $defaultModules['user'] = 'ready'; 02821 } 02822 } else { 02823 // User JS disabled 02824 $defaultModules['user'] = 'missing'; 02825 } 02826 02827 // Group JS is only enabled if site JS is enabled. 02828 if ( $wgUseSiteJs ) { 02829 if ( $this->getUser()->isLoggedIn() ) { 02830 $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED, 02831 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02832 ); 02833 $defaultModules['user.groups'] = 'loading'; 02834 } else { 02835 // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to 02836 // avoid blocking gadgets that might depend upon the module. 02837 $defaultModules['user.groups'] = 'ready'; 02838 } 02839 } else { 02840 // Site (and group JS) disabled 02841 $defaultModules['user.groups'] = 'missing'; 02842 } 02843 02844 $loaderInit = ''; 02845 if ( $inHead ) { 02846 // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'. 02847 foreach ( $defaultModules as $m => $state ) { 02848 if ( $state == 'loading' ) { 02849 unset( $defaultModules[$m] ); 02850 } 02851 } 02852 } 02853 if ( count( $defaultModules ) > 0 ) { 02854 $loaderInit = Html::inlineScript( 02855 ResourceLoader::makeLoaderConditionalScript( 02856 ResourceLoader::makeLoaderStateScript( $defaultModules ) 02857 ) 02858 ) . "\n"; 02859 } 02860 return $loaderInit . $scripts; 02861 } 02862 02867 function getBottomScripts() { 02868 global $wgResourceLoaderExperimentalAsyncLoading; 02869 if ( !$wgResourceLoaderExperimentalAsyncLoading ) { 02870 return $this->getScriptsForBottomQueue( false ); 02871 } else { 02872 return ''; 02873 } 02874 } 02875 02882 public function addJsConfigVars( $keys, $value = null ) { 02883 if ( is_array( $keys ) ) { 02884 foreach ( $keys as $key => $value ) { 02885 $this->mJsConfigVars[$key] = $value; 02886 } 02887 return; 02888 } 02889 02890 $this->mJsConfigVars[$keys] = $value; 02891 } 02892 02893 02906 public function getJSVars() { 02907 global $wgUseAjax, $wgContLang; 02908 02909 $latestRevID = 0; 02910 $pageID = 0; 02911 $canonicalName = false; # bug 21115 02912 02913 $title = $this->getTitle(); 02914 $ns = $title->getNamespace(); 02915 $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText(); 02916 02917 // Get the relevant title so that AJAX features can use the correct page name 02918 // when making API requests from certain special pages (bug 34972). 02919 $relevantTitle = $this->getSkin()->getRelevantTitle(); 02920 02921 if ( $ns == NS_SPECIAL ) { 02922 list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); 02923 } elseif ( $this->canUseWikiPage() ) { 02924 $wikiPage = $this->getWikiPage(); 02925 $latestRevID = $wikiPage->getLatest(); 02926 $pageID = $wikiPage->getId(); 02927 } 02928 02929 $lang = $title->getPageLanguage(); 02930 02931 // Pre-process information 02932 $separatorTransTable = $lang->separatorTransformTable(); 02933 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); 02934 $compactSeparatorTransTable = array( 02935 implode( "\t", array_keys( $separatorTransTable ) ), 02936 implode( "\t", $separatorTransTable ), 02937 ); 02938 $digitTransTable = $lang->digitTransformTable(); 02939 $digitTransTable = $digitTransTable ? $digitTransTable : array(); 02940 $compactDigitTransTable = array( 02941 implode( "\t", array_keys( $digitTransTable ) ), 02942 implode( "\t", $digitTransTable ), 02943 ); 02944 02945 $vars = array( 02946 'wgCanonicalNamespace' => $nsname, 02947 'wgCanonicalSpecialPageName' => $canonicalName, 02948 'wgNamespaceNumber' => $title->getNamespace(), 02949 'wgPageName' => $title->getPrefixedDBKey(), 02950 'wgTitle' => $title->getText(), 02951 'wgCurRevisionId' => $latestRevID, 02952 'wgArticleId' => $pageID, 02953 'wgIsArticle' => $this->isArticle(), 02954 'wgAction' => Action::getActionName( $this->getContext() ), 02955 'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(), 02956 'wgUserGroups' => $this->getUser()->getEffectiveGroups(), 02957 'wgCategories' => $this->getCategories(), 02958 'wgBreakFrames' => $this->getFrameOptions() == 'DENY', 02959 'wgPageContentLanguage' => $lang->getCode(), 02960 'wgSeparatorTransformTable' => $compactSeparatorTransTable, 02961 'wgDigitTransformTable' => $compactDigitTransTable, 02962 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(), 02963 'wgMonthNames' => $lang->getMonthNamesArray(), 02964 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(), 02965 'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(), 02966 ); 02967 if ( $wgContLang->hasVariants() ) { 02968 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); 02969 } 02970 foreach ( $title->getRestrictionTypes() as $type ) { 02971 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type ); 02972 } 02973 if ( $title->isMainPage() ) { 02974 $vars['wgIsMainPage'] = true; 02975 } 02976 if ( $this->mRedirectedFrom ) { 02977 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey(); 02978 } 02979 02980 // Allow extensions to add their custom variables to the mw.config map. 02981 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not 02982 // page-dependant but site-wide (without state). 02983 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. 02984 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) ); 02985 02986 // Merge in variables from addJsConfigVars last 02987 return array_merge( $vars, $this->mJsConfigVars ); 02988 } 02989 02999 public function userCanPreview() { 03000 if ( $this->getRequest()->getVal( 'action' ) != 'submit' 03001 || !$this->getRequest()->wasPosted() 03002 || !$this->getUser()->matchEditToken( 03003 $this->getRequest()->getVal( 'wpEditToken' ) ) 03004 ) { 03005 return false; 03006 } 03007 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) { 03008 return false; 03009 } 03010 03011 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) ); 03012 } 03013 03019 public function getHeadLinksArray( $addContentType = false ) { 03020 global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI, 03021 $wgSitename, $wgVersion, $wgHtml5, $wgMimeType, 03022 $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes, 03023 $wgDisableLangConversion, $wgCanonicalLanguageLinks, 03024 $wgRightsPage, $wgRightsUrl; 03025 03026 $tags = array(); 03027 03028 if ( $addContentType ) { 03029 if ( $wgHtml5 ) { 03030 # More succinct than <meta http-equiv=Content-Type>, has the 03031 # same effect 03032 $tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) ); 03033 } else { 03034 $tags['meta-content-type'] = Html::element( 'meta', array( 03035 'http-equiv' => 'Content-Type', 03036 'content' => "$wgMimeType; charset=UTF-8" 03037 ) ); 03038 $tags['meta-content-style-type'] = Html::element( 'meta', array( // bug 15835 03039 'http-equiv' => 'Content-Style-Type', 03040 'content' => 'text/css' 03041 ) ); 03042 } 03043 } 03044 03045 $tags['meta-generator'] = Html::element( 'meta', array( 03046 'name' => 'generator', 03047 'content' => "MediaWiki $wgVersion", 03048 ) ); 03049 03050 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}"; 03051 if( $p !== 'index,follow' ) { 03052 // http://www.robotstxt.org/wc/meta-user.html 03053 // Only show if it's different from the default robots policy 03054 $tags['meta-robots'] = Html::element( 'meta', array( 03055 'name' => 'robots', 03056 'content' => $p, 03057 ) ); 03058 } 03059 03060 if ( count( $this->mKeywords ) > 0 ) { 03061 $strip = array( 03062 "/<.*?" . ">/" => '', 03063 "/_/" => ' ' 03064 ); 03065 $tags['meta-keywords'] = Html::element( 'meta', array( 03066 'name' => 'keywords', 03067 'content' => preg_replace( 03068 array_keys( $strip ), 03069 array_values( $strip ), 03070 implode( ',', $this->mKeywords ) 03071 ) 03072 ) ); 03073 } 03074 03075 foreach ( $this->mMetatags as $tag ) { 03076 if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) { 03077 $a = 'http-equiv'; 03078 $tag[0] = substr( $tag[0], 5 ); 03079 } else { 03080 $a = 'name'; 03081 } 03082 $tagName = "meta-{$tag[0]}"; 03083 if ( isset( $tags[$tagName] ) ) { 03084 $tagName .= $tag[1]; 03085 } 03086 $tags[$tagName] = Html::element( 'meta', 03087 array( 03088 $a => $tag[0], 03089 'content' => $tag[1] 03090 ) 03091 ); 03092 } 03093 03094 foreach ( $this->mLinktags as $tag ) { 03095 $tags[] = Html::element( 'link', $tag ); 03096 } 03097 03098 # Universal edit button 03099 if ( $wgUniversalEditButton && $this->isArticleRelated() ) { 03100 $user = $this->getUser(); 03101 if ( $this->getTitle()->quickUserCan( 'edit', $user ) 03102 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) { 03103 // Original UniversalEditButton 03104 $msg = $this->msg( 'edit' )->text(); 03105 $tags['universal-edit-button'] = Html::element( 'link', array( 03106 'rel' => 'alternate', 03107 'type' => 'application/x-wiki', 03108 'title' => $msg, 03109 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03110 ) ); 03111 // Alternate edit link 03112 $tags['alternative-edit'] = Html::element( 'link', array( 03113 'rel' => 'edit', 03114 'title' => $msg, 03115 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03116 ) ); 03117 } 03118 } 03119 03120 # Generally the order of the favicon and apple-touch-icon links 03121 # should not matter, but Konqueror (3.5.9 at least) incorrectly 03122 # uses whichever one appears later in the HTML source. Make sure 03123 # apple-touch-icon is specified first to avoid this. 03124 if ( $wgAppleTouchIcon !== false ) { 03125 $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) ); 03126 } 03127 03128 if ( $wgFavicon !== false ) { 03129 $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) ); 03130 } 03131 03132 # OpenSearch description link 03133 $tags['opensearch'] = Html::element( 'link', array( 03134 'rel' => 'search', 03135 'type' => 'application/opensearchdescription+xml', 03136 'href' => wfScript( 'opensearch_desc' ), 03137 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(), 03138 ) ); 03139 03140 if ( $wgEnableAPI ) { 03141 # Real Simple Discovery link, provides auto-discovery information 03142 # for the MediaWiki API (and potentially additional custom API 03143 # support such as WordPress or Twitter-compatible APIs for a 03144 # blogging extension, etc) 03145 $tags['rsd'] = Html::element( 'link', array( 03146 'rel' => 'EditURI', 03147 'type' => 'application/rsd+xml', 03148 // Output a protocol-relative URL here if $wgServer is protocol-relative 03149 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though 03150 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ), 03151 ) ); 03152 } 03153 03154 03155 # Language variants 03156 if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) { 03157 $lang = $this->getTitle()->getPageLanguage(); 03158 if ( $lang->hasVariants() ) { 03159 03160 $urlvar = $lang->getURLVariant(); 03161 03162 if ( !$urlvar ) { 03163 $variants = $lang->getVariants(); 03164 foreach ( $variants as $_v ) { 03165 $tags["variant-$_v"] = Html::element( 'link', array( 03166 'rel' => 'alternate', 03167 'hreflang' => $_v, 03168 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) ) 03169 ); 03170 } 03171 } else { 03172 $tags['canonical'] = Html::element( 'link', array( 03173 'rel' => 'canonical', 03174 'href' => $this->getTitle()->getCanonicalUrl() 03175 ) ); 03176 } 03177 } 03178 } 03179 03180 # Copyright 03181 $copyright = ''; 03182 if ( $wgRightsPage ) { 03183 $copy = Title::newFromText( $wgRightsPage ); 03184 03185 if ( $copy ) { 03186 $copyright = $copy->getLocalURL(); 03187 } 03188 } 03189 03190 if ( !$copyright && $wgRightsUrl ) { 03191 $copyright = $wgRightsUrl; 03192 } 03193 03194 if ( $copyright ) { 03195 $tags['copyright'] = Html::element( 'link', array( 03196 'rel' => 'copyright', 03197 'href' => $copyright ) 03198 ); 03199 } 03200 03201 # Feeds 03202 if ( $wgFeed ) { 03203 foreach( $this->getSyndicationLinks() as $format => $link ) { 03204 # Use the page name for the title. In principle, this could 03205 # lead to issues with having the same name for different feeds 03206 # corresponding to the same page, but we can't avoid that at 03207 # this low a level. 03208 03209 $tags[] = $this->feedLink( 03210 $format, 03211 $link, 03212 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep) 03213 $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text() 03214 ); 03215 } 03216 03217 # Recent changes feed should appear on every page (except recentchanges, 03218 # that would be redundant). Put it after the per-page feed to avoid 03219 # changing existing behavior. It's still available, probably via a 03220 # menu in your browser. Some sites might have a different feed they'd 03221 # like to promote instead of the RC feed (maybe like a "Recent New Articles" 03222 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined. 03223 # If so, use it instead. 03224 if ( $wgOverrideSiteFeed ) { 03225 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) { 03226 // Note, this->feedLink escapes the url. 03227 $tags[] = $this->feedLink( 03228 $type, 03229 $feedUrl, 03230 $this->msg( "site-{$type}-feed", $wgSitename )->text() 03231 ); 03232 } 03233 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) { 03234 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' ); 03235 foreach ( $wgAdvertisedFeedTypes as $format ) { 03236 $tags[] = $this->feedLink( 03237 $format, 03238 $rctitle->getLocalURL( "feed={$format}" ), 03239 $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'. 03240 ); 03241 } 03242 } 03243 } 03244 return $tags; 03245 } 03246 03253 public function getHeadLinks( $unused = null, $addContentType = false ) { 03254 return implode( "\n", $this->getHeadLinksArray( $addContentType ) ); 03255 } 03256 03265 private function feedLink( $type, $url, $text ) { 03266 return Html::element( 'link', array( 03267 'rel' => 'alternate', 03268 'type' => "application/$type+xml", 03269 'title' => $text, 03270 'href' => $url ) 03271 ); 03272 } 03273 03283 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) { 03284 $options = array(); 03285 // Even though we expect the media type to be lowercase, but here we 03286 // force it to lowercase to be safe. 03287 if( $media ) { 03288 $options['media'] = $media; 03289 } 03290 if( $condition ) { 03291 $options['condition'] = $condition; 03292 } 03293 if( $dir ) { 03294 $options['dir'] = $dir; 03295 } 03296 $this->styles[$style] = $options; 03297 } 03298 03304 public function addInlineStyle( $style_css, $flip = 'noflip' ) { 03305 if( $flip === 'flip' && $this->getLanguage()->isRTL() ) { 03306 # If wanted, and the interface is right-to-left, flip the CSS 03307 $style_css = CSSJanus::transform( $style_css, true, false ); 03308 } 03309 $this->mInlineStyles .= Html::inlineStyle( $style_css ); 03310 } 03311 03318 public function buildCssLinks() { 03319 global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, 03320 $wgLang, $wgContLang; 03321 03322 $this->getSkin()->setupSkinUserCss( $this ); 03323 03324 // Add ResourceLoader styles 03325 // Split the styles into four groups 03326 $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() ); 03327 $otherTags = ''; // Tags to append after the normal <link> tags 03328 $resourceLoader = $this->getResourceLoader(); 03329 03330 $moduleStyles = $this->getModuleStyles(); 03331 03332 // Per-site custom styles 03333 if ( $wgUseSiteCss ) { 03334 $moduleStyles[] = 'site'; 03335 $moduleStyles[] = 'noscript'; 03336 if( $this->getUser()->isLoggedIn() ){ 03337 $moduleStyles[] = 'user.groups'; 03338 } 03339 } 03340 03341 // Per-user custom styles 03342 if ( $wgAllowUserCss ) { 03343 if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) { 03344 // We're on a preview of a CSS subpage 03345 // Exclude this page from the user module in case it's in there (bug 26283) 03346 $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false, 03347 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ) 03348 ); 03349 03350 // Load the previewed CSS 03351 // If needed, Janus it first. This is user-supplied CSS, so it's 03352 // assumed to be right for the content language directionality. 03353 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' ); 03354 if ( $wgLang->getDir() !== $wgContLang->getDir() ) { 03355 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false ); 03356 } 03357 $otherTags .= Html::inlineStyle( $previewedCSS ); 03358 } else { 03359 // Load the user styles normally 03360 $moduleStyles[] = 'user'; 03361 } 03362 } 03363 03364 // Per-user preference styles 03365 if ( $wgAllowUserCssPrefs ) { 03366 $moduleStyles[] = 'user.cssprefs'; 03367 } 03368 03369 foreach ( $moduleStyles as $name ) { 03370 $module = $resourceLoader->getModule( $name ); 03371 if ( !$module ) { 03372 continue; 03373 } 03374 $group = $module->getGroup(); 03375 // Modules in groups named "other" or anything different than "user", "site" or "private" 03376 // will be placed in the "other" group 03377 $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name; 03378 } 03379 03380 // We want site, private and user styles to override dynamically added styles from modules, but we want 03381 // dynamically added styles to override statically added styles from other modules. So the order 03382 // has to be other, dynamic, site, private, user 03383 // Add statically added styles for other modules 03384 $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES ); 03385 // Add normal styles added through addStyle()/addInlineStyle() here 03386 $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles; 03387 // Add marker tag to mark the place where the client-side loader should inject dynamic styles 03388 // We use a <meta> tag with a made-up name for this because that's valid HTML 03389 $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n"; 03390 03391 // Add site, private and user styles 03392 // 'private' at present only contains user.options, so put that before 'user' 03393 // Any future private modules will likely have a similar user-specific character 03394 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) { 03395 $ret .= $this->makeResourceLoaderLink( $styles[$group], 03396 ResourceLoaderModule::TYPE_STYLES 03397 ); 03398 } 03399 03400 // Add stuff in $otherTags (previewed user CSS if applicable) 03401 $ret .= $otherTags; 03402 return $ret; 03403 } 03404 03408 public function buildCssLinksArray() { 03409 $links = array(); 03410 03411 // Add any extension CSS 03412 foreach ( $this->mExtStyles as $url ) { 03413 $this->addStyle( $url ); 03414 } 03415 $this->mExtStyles = array(); 03416 03417 foreach( $this->styles as $file => $options ) { 03418 $link = $this->styleLink( $file, $options ); 03419 if( $link ) { 03420 $links[$file] = $link; 03421 } 03422 } 03423 return $links; 03424 } 03425 03434 protected function styleLink( $style, $options ) { 03435 if( isset( $options['dir'] ) ) { 03436 if( $this->getLanguage()->getDir() != $options['dir'] ) { 03437 return ''; 03438 } 03439 } 03440 03441 if( isset( $options['media'] ) ) { 03442 $media = self::transformCssMedia( $options['media'] ); 03443 if( is_null( $media ) ) { 03444 return ''; 03445 } 03446 } else { 03447 $media = 'all'; 03448 } 03449 03450 if( substr( $style, 0, 1 ) == '/' || 03451 substr( $style, 0, 5 ) == 'http:' || 03452 substr( $style, 0, 6 ) == 'https:' ) { 03453 $url = $style; 03454 } else { 03455 global $wgStylePath, $wgStyleVersion; 03456 $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion; 03457 } 03458 03459 $link = Html::linkedStyle( $url, $media ); 03460 03461 if( isset( $options['condition'] ) ) { 03462 $condition = htmlspecialchars( $options['condition'] ); 03463 $link = "<!--[if $condition]>$link<![endif]-->"; 03464 } 03465 return $link; 03466 } 03467 03474 public static function transformCssMedia( $media ) { 03475 global $wgRequest, $wgHandheldForIPhone; 03476 03477 // Switch in on-screen display for media testing 03478 $switches = array( 03479 'printable' => 'print', 03480 'handheld' => 'handheld', 03481 ); 03482 foreach( $switches as $switch => $targetMedia ) { 03483 if( $wgRequest->getBool( $switch ) ) { 03484 if( $media == $targetMedia ) { 03485 $media = ''; 03486 } elseif( $media == 'screen' ) { 03487 return null; 03488 } 03489 } 03490 } 03491 03492 // Expand longer media queries as iPhone doesn't grok 'handheld' 03493 if( $wgHandheldForIPhone ) { 03494 $mediaAliases = array( 03495 'screen' => 'screen and (min-device-width: 481px)', 03496 'handheld' => 'handheld, only screen and (max-device-width: 480px)', 03497 ); 03498 03499 if( isset( $mediaAliases[$media] ) ) { 03500 $media = $mediaAliases[$media]; 03501 } 03502 } 03503 03504 return $media; 03505 } 03506 03513 public function addWikiMsg( /*...*/ ) { 03514 $args = func_get_args(); 03515 $name = array_shift( $args ); 03516 $this->addWikiMsgArray( $name, $args ); 03517 } 03518 03527 public function addWikiMsgArray( $name, $args ) { 03528 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() ); 03529 } 03530 03554 public function wrapWikiMsg( $wrap /*, ...*/ ) { 03555 $msgSpecs = func_get_args(); 03556 array_shift( $msgSpecs ); 03557 $msgSpecs = array_values( $msgSpecs ); 03558 $s = $wrap; 03559 foreach ( $msgSpecs as $n => $spec ) { 03560 $options = array(); 03561 if ( is_array( $spec ) ) { 03562 $args = $spec; 03563 $name = array_shift( $args ); 03564 if ( isset( $args['options'] ) ) { 03565 unset( $args['options'] ); 03566 wfDeprecated( 03567 'Adding "options" to ' . __METHOD__ . ' is no longer supported', 03568 '1.20' 03569 ); 03570 } 03571 } else { 03572 $args = array(); 03573 $name = $spec; 03574 } 03575 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s ); 03576 } 03577 $this->addWikiText( $s ); 03578 } 03579 03589 public function includeJQuery( $modules = array() ) { 03590 return array(); 03591 } 03592 03593 }