MediaWiki
master
|
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__, '1.18' ); 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 00859 public function setSubtitle( $str ) { 00860 $this->clearSubtitle(); 00861 $this->addSubtitle( $str ); 00862 } 00863 00870 public function appendSubtitle( $str ) { 00871 $this->addSubtitle( $str ); 00872 } 00873 00879 public function addSubtitle( $str ) { 00880 if ( $str instanceof Message ) { 00881 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse(); 00882 } else { 00883 $this->mSubtitle[] = $str; 00884 } 00885 } 00886 00892 public function addBacklinkSubtitle( Title $title ) { 00893 $query = array(); 00894 if ( $title->isRedirect() ) { 00895 $query['redirect'] = 'no'; 00896 } 00897 $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) ); 00898 } 00899 00903 public function clearSubtitle() { 00904 $this->mSubtitle = array(); 00905 } 00906 00912 public function getSubtitle() { 00913 return implode( "<br />\n\t\t\t\t", $this->mSubtitle ); 00914 } 00915 00920 public function setPrintable() { 00921 $this->mPrintable = true; 00922 } 00923 00929 public function isPrintable() { 00930 return $this->mPrintable; 00931 } 00932 00936 public function disable() { 00937 $this->mDoNothing = true; 00938 } 00939 00945 public function isDisabled() { 00946 return $this->mDoNothing; 00947 } 00948 00954 public function showNewSectionLink() { 00955 return $this->mNewSectionLink; 00956 } 00957 00963 public function forceHideNewSectionLink() { 00964 return $this->mHideNewSectionLink; 00965 } 00966 00975 public function setSyndicated( $show = true ) { 00976 if ( $show ) { 00977 $this->setFeedAppendQuery( false ); 00978 } else { 00979 $this->mFeedLinks = array(); 00980 } 00981 } 00982 00992 public function setFeedAppendQuery( $val ) { 00993 global $wgAdvertisedFeedTypes; 00994 00995 $this->mFeedLinks = array(); 00996 00997 foreach ( $wgAdvertisedFeedTypes as $type ) { 00998 $query = "feed=$type"; 00999 if ( is_string( $val ) ) { 01000 $query .= '&' . $val; 01001 } 01002 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query ); 01003 } 01004 } 01005 01012 public function addFeedLink( $format, $href ) { 01013 global $wgAdvertisedFeedTypes; 01014 01015 if ( in_array( $format, $wgAdvertisedFeedTypes ) ) { 01016 $this->mFeedLinks[$format] = $href; 01017 } 01018 } 01019 01024 public function isSyndicated() { 01025 return count( $this->mFeedLinks ) > 0; 01026 } 01027 01032 public function getSyndicationLinks() { 01033 return $this->mFeedLinks; 01034 } 01035 01041 public function getFeedAppendQuery() { 01042 return $this->mFeedLinksAppendQuery; 01043 } 01044 01052 public function setArticleFlag( $v ) { 01053 $this->mIsarticle = $v; 01054 if ( $v ) { 01055 $this->mIsArticleRelated = $v; 01056 } 01057 } 01058 01065 public function isArticle() { 01066 return $this->mIsarticle; 01067 } 01068 01075 public function setArticleRelated( $v ) { 01076 $this->mIsArticleRelated = $v; 01077 if ( !$v ) { 01078 $this->mIsarticle = false; 01079 } 01080 } 01081 01087 public function isArticleRelated() { 01088 return $this->mIsArticleRelated; 01089 } 01090 01097 public function addLanguageLinks( $newLinkArray ) { 01098 $this->mLanguageLinks += $newLinkArray; 01099 } 01100 01107 public function setLanguageLinks( $newLinkArray ) { 01108 $this->mLanguageLinks = $newLinkArray; 01109 } 01110 01116 public function getLanguageLinks() { 01117 return $this->mLanguageLinks; 01118 } 01119 01125 public function addCategoryLinks( $categories ) { 01126 global $wgContLang; 01127 01128 if ( !is_array( $categories ) || count( $categories ) == 0 ) { 01129 return; 01130 } 01131 01132 # Add the links to a LinkBatch 01133 $arr = array( NS_CATEGORY => $categories ); 01134 $lb = new LinkBatch; 01135 $lb->setArray( $arr ); 01136 01137 # Fetch existence plus the hiddencat property 01138 $dbr = wfGetDB( DB_SLAVE ); 01139 $res = $dbr->select( array( 'page', 'page_props' ), 01140 array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ), 01141 $lb->constructSet( 'page', $dbr ), 01142 __METHOD__, 01143 array(), 01144 array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) ) 01145 ); 01146 01147 # Add the results to the link cache 01148 $lb->addResultToCache( LinkCache::singleton(), $res ); 01149 01150 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+ 01151 $categories = array_combine( 01152 array_keys( $categories ), 01153 array_fill( 0, count( $categories ), 'normal' ) 01154 ); 01155 01156 # Mark hidden categories 01157 foreach ( $res as $row ) { 01158 if ( isset( $row->pp_value ) ) { 01159 $categories[$row->page_title] = 'hidden'; 01160 } 01161 } 01162 01163 # Add the remaining categories to the skin 01164 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) { 01165 foreach ( $categories as $category => $type ) { 01166 $origcategory = $category; 01167 $title = Title::makeTitleSafe( NS_CATEGORY, $category ); 01168 $wgContLang->findVariantLink( $category, $title, true ); 01169 if ( $category != $origcategory ) { 01170 if ( array_key_exists( $category, $categories ) ) { 01171 continue; 01172 } 01173 } 01174 $text = $wgContLang->convertHtml( $title->getText() ); 01175 $this->mCategories[] = $title->getText(); 01176 $this->mCategoryLinks[$type][] = Linker::link( $title, $text ); 01177 } 01178 } 01179 } 01180 01186 public function setCategoryLinks( $categories ) { 01187 $this->mCategoryLinks = array(); 01188 $this->addCategoryLinks( $categories ); 01189 } 01190 01199 public function getCategoryLinks() { 01200 return $this->mCategoryLinks; 01201 } 01202 01208 public function getCategories() { 01209 return $this->mCategories; 01210 } 01211 01216 public function disallowUserJs() { 01217 $this->reduceAllowedModules( 01218 ResourceLoaderModule::TYPE_SCRIPTS, 01219 ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL 01220 ); 01221 } 01222 01230 public function isUserJsAllowed() { 01231 wfDeprecated( __METHOD__, '1.18' ); 01232 return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL; 01233 } 01234 01241 public function getAllowedModules( $type ){ 01242 if( $type == ResourceLoaderModule::TYPE_COMBINED ){ 01243 return min( array_values( $this->mAllowedModules ) ); 01244 } else { 01245 return isset( $this->mAllowedModules[$type] ) 01246 ? $this->mAllowedModules[$type] 01247 : ResourceLoaderModule::ORIGIN_ALL; 01248 } 01249 } 01250 01256 public function setAllowedModules( $type, $level ){ 01257 $this->mAllowedModules[$type] = $level; 01258 } 01259 01265 public function reduceAllowedModules( $type, $level ){ 01266 $this->mAllowedModules[$type] = min( $this->getAllowedModules($type), $level ); 01267 } 01268 01274 public function prependHTML( $text ) { 01275 $this->mBodytext = $text . $this->mBodytext; 01276 } 01277 01283 public function addHTML( $text ) { 01284 $this->mBodytext .= $text; 01285 } 01286 01296 public function addElement( $element, $attribs = array(), $contents = '' ) { 01297 $this->addHTML( Html::element( $element, $attribs, $contents ) ); 01298 } 01299 01303 public function clearHTML() { 01304 $this->mBodytext = ''; 01305 } 01306 01312 public function getHTML() { 01313 return $this->mBodytext; 01314 } 01315 01323 public function parserOptions( $options = null ) { 01324 if ( !$this->mParserOptions ) { 01325 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() ); 01326 $this->mParserOptions->setEditSection( false ); 01327 } 01328 return wfSetVar( $this->mParserOptions, $options ); 01329 } 01330 01338 public function setRevisionId( $revid ) { 01339 $val = is_null( $revid ) ? null : intval( $revid ); 01340 return wfSetVar( $this->mRevisionId, $val ); 01341 } 01342 01348 public function getRevisionId() { 01349 return $this->mRevisionId; 01350 } 01351 01359 public function setRevisionTimestamp( $timestamp) { 01360 return wfSetVar( $this->mRevisionTimestamp, $timestamp ); 01361 } 01362 01369 public function getRevisionTimestamp() { 01370 return $this->mRevisionTimestamp; 01371 } 01372 01379 public function setFileVersion( $file ) { 01380 $val = null; 01381 if ( $file instanceof File && $file->exists() ) { 01382 $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ); 01383 } 01384 return wfSetVar( $this->mFileVersion, $val, true ); 01385 } 01386 01392 public function getFileVersion() { 01393 return $this->mFileVersion; 01394 } 01395 01402 public function getTemplateIds() { 01403 return $this->mTemplateIds; 01404 } 01405 01412 public function getFileSearchOptions() { 01413 return $this->mImageTimeKeys; 01414 } 01415 01424 public function addWikiText( $text, $linestart = true, $interface = true ) { 01425 $title = $this->getTitle(); // Work arround E_STRICT 01426 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface ); 01427 } 01428 01436 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) { 01437 $this->addWikiTextTitle( $text, $title, $linestart ); 01438 } 01439 01447 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) { 01448 $this->addWikiTextTitle( $text, $title, $linestart, true ); 01449 } 01450 01457 public function addWikiTextTidy( $text, $linestart = true ) { 01458 $title = $this->getTitle(); 01459 $this->addWikiTextTitleTidy( $text, $title, $linestart ); 01460 } 01461 01472 public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false, $interface = false ) { 01473 global $wgParser; 01474 01475 wfProfileIn( __METHOD__ ); 01476 01477 $popts = $this->parserOptions(); 01478 $oldTidy = $popts->setTidy( $tidy ); 01479 $popts->setInterfaceMessage( (bool) $interface ); 01480 01481 $parserOutput = $wgParser->parse( 01482 $text, $title, $popts, 01483 $linestart, true, $this->mRevisionId 01484 ); 01485 01486 $popts->setTidy( $oldTidy ); 01487 01488 $this->addParserOutput( $parserOutput ); 01489 01490 wfProfileOut( __METHOD__ ); 01491 } 01492 01498 public function addParserOutputNoText( &$parserOutput ) { 01499 $this->mLanguageLinks += $parserOutput->getLanguageLinks(); 01500 $this->addCategoryLinks( $parserOutput->getCategories() ); 01501 $this->mNewSectionLink = $parserOutput->getNewSection(); 01502 $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); 01503 01504 $this->mParseWarnings = $parserOutput->getWarnings(); 01505 if ( !$parserOutput->isCacheable() ) { 01506 $this->enableClientCache( false ); 01507 } 01508 $this->mNoGallery = $parserOutput->getNoGallery(); 01509 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() ); 01510 $this->addModules( $parserOutput->getModules() ); 01511 $this->addModuleScripts( $parserOutput->getModuleScripts() ); 01512 $this->addModuleStyles( $parserOutput->getModuleStyles() ); 01513 $this->addModuleMessages( $parserOutput->getModuleMessages() ); 01514 01515 // Template versioning... 01516 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) { 01517 if ( isset( $this->mTemplateIds[$ns] ) ) { 01518 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns]; 01519 } else { 01520 $this->mTemplateIds[$ns] = $dbks; 01521 } 01522 } 01523 // File versioning... 01524 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) { 01525 $this->mImageTimeKeys[$dbk] = $data; 01526 } 01527 01528 // Hooks registered in the object 01529 global $wgParserOutputHooks; 01530 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) { 01531 list( $hookName, $data ) = $hookInfo; 01532 if ( isset( $wgParserOutputHooks[$hookName] ) ) { 01533 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data ); 01534 } 01535 } 01536 01537 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); 01538 } 01539 01545 function addParserOutput( &$parserOutput ) { 01546 $this->addParserOutputNoText( $parserOutput ); 01547 $text = $parserOutput->getText(); 01548 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) ); 01549 $this->addHTML( $text ); 01550 } 01551 01552 01558 public function addTemplate( &$template ) { 01559 ob_start(); 01560 $template->execute(); 01561 $this->addHTML( ob_get_contents() ); 01562 ob_end_clean(); 01563 } 01564 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__, '1.18' ); 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 01998 ob_end_flush(); 01999 02000 wfProfileOut( __METHOD__ ); 02001 } 02002 02008 public function out( $ins ) { 02009 print $ins; 02010 } 02011 02016 function blockedPage() { 02017 throw new UserBlockedError( $this->getUser()->mBlock ); 02018 } 02019 02030 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) { 02031 $this->setPageTitle( $pageTitle ); 02032 if ( $htmlTitle !== false ) { 02033 $this->setHTMLTitle( $htmlTitle ); 02034 } 02035 $this->setRobotPolicy( 'noindex,nofollow' ); 02036 $this->setArticleRelated( false ); 02037 $this->enableClientCache( false ); 02038 $this->mRedirect = ''; 02039 $this->clearSubtitle(); 02040 $this->clearHTML(); 02041 } 02042 02054 public function showErrorPage( $title, $msg, $params = array() ) { 02055 if( !$title instanceof Message ) { 02056 $title = $this->msg( $title ); 02057 } 02058 02059 $this->prepareErrorPage( $title ); 02060 02061 if ( $msg instanceof Message ){ 02062 $this->addHTML( $msg->parseAsBlock() ); 02063 } else { 02064 $this->addWikiMsgArray( $msg, $params ); 02065 } 02066 02067 $this->returnToMain(); 02068 } 02069 02076 public function showPermissionsErrorPage( $errors, $action = null ) { 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 && ( User::groupHasPermission( 'user', $action ) 02086 || User::groupHasPermission( '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 02161 public function permissionRequired( $permission ) { 02162 throw new PermissionsError( $permission ); 02163 } 02164 02170 public function loginToUse() { 02171 throw new PermissionsError( 'read' ); 02172 } 02173 02181 public function formatPermissionsErrorMessage( $errors, $action = null ) { 02182 if ( $action == null ) { 02183 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n"; 02184 } else { 02185 $action_desc = $this->msg( "action-$action" )->plain(); 02186 $text = $this->msg( 02187 'permissionserrorstext-withaction', 02188 count( $errors ), 02189 $action_desc 02190 )->plain() . "\n\n"; 02191 } 02192 02193 if ( count( $errors ) > 1 ) { 02194 $text .= '<ul class="permissions-errors">' . "\n"; 02195 02196 foreach( $errors as $error ) { 02197 $text .= '<li>'; 02198 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain(); 02199 $text .= "</li>\n"; 02200 } 02201 $text .= '</ul>'; 02202 } else { 02203 $text .= "<div class=\"permissions-errors\">\n" . 02204 call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() . 02205 "\n</div>"; 02206 } 02207 02208 return $text; 02209 } 02210 02232 public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) { 02233 $this->setRobotPolicy( 'noindex,nofollow' ); 02234 $this->setArticleRelated( false ); 02235 02236 // If no reason is given, just supply a default "I can't let you do 02237 // that, Dave" message. Should only occur if called by legacy code. 02238 if ( $protected && empty( $reasons ) ) { 02239 $reasons[] = array( 'badaccess-group0' ); 02240 } 02241 02242 if ( !empty( $reasons ) ) { 02243 // Permissions error 02244 if( $source ) { 02245 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) ); 02246 $this->addBacklinkSubtitle( $this->getTitle() ); 02247 } else { 02248 $this->setPageTitle( $this->msg( 'badaccess' ) ); 02249 } 02250 $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) ); 02251 } else { 02252 // Wiki is read only 02253 throw new ReadOnlyError; 02254 } 02255 02256 // Show source, if supplied 02257 if( is_string( $source ) ) { 02258 $this->addWikiMsg( 'viewsourcetext' ); 02259 02260 $pageLang = $this->getTitle()->getPageLanguage(); 02261 $params = array( 02262 'id' => 'wpTextbox1', 02263 'name' => 'wpTextbox1', 02264 'cols' => $this->getUser()->getOption( 'cols' ), 02265 'rows' => $this->getUser()->getOption( 'rows' ), 02266 'readonly' => 'readonly', 02267 'lang' => $pageLang->getHtmlCode(), 02268 'dir' => $pageLang->getDir(), 02269 ); 02270 $this->addHTML( Html::element( 'textarea', $params, $source ) ); 02271 02272 // Show templates used by this article 02273 $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() ); 02274 $this->addHTML( "<div class='templatesUsed'> 02275 $templates 02276 </div> 02277 " ); 02278 } 02279 02280 # If the title doesn't exist, it's fairly pointless to print a return 02281 # link to it. After all, you just tried editing it and couldn't, so 02282 # what's there to do there? 02283 if( $this->getTitle()->exists() ) { 02284 $this->returnToMain( null, $this->getTitle() ); 02285 } 02286 } 02287 02292 public function rateLimited() { 02293 throw new ThrottledError; 02294 } 02295 02305 public function showLagWarning( $lag ) { 02306 global $wgSlaveLagWarning, $wgSlaveLagCritical; 02307 if( $lag >= $wgSlaveLagWarning ) { 02308 $message = $lag < $wgSlaveLagCritical 02309 ? 'lag-warn-normal' 02310 : 'lag-warn-high'; 02311 $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" ); 02312 $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) ); 02313 } 02314 } 02315 02316 public function showFatalError( $message ) { 02317 $this->prepareErrorPage( $this->msg( 'internalerror' ) ); 02318 02319 $this->addHTML( $message ); 02320 } 02321 02322 public function showUnexpectedValueError( $name, $val ) { 02323 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() ); 02324 } 02325 02326 public function showFileCopyError( $old, $new ) { 02327 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() ); 02328 } 02329 02330 public function showFileRenameError( $old, $new ) { 02331 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() ); 02332 } 02333 02334 public function showFileDeleteError( $name ) { 02335 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() ); 02336 } 02337 02338 public function showFileNotFoundError( $name ) { 02339 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() ); 02340 } 02341 02350 public function addReturnTo( $title, $query = array(), $text = null, $options = array() ) { 02351 if( in_array( 'http', $options ) ) { 02352 $proto = PROTO_HTTP; 02353 } elseif( in_array( 'https', $options ) ) { 02354 $proto = PROTO_HTTPS; 02355 } else { 02356 $proto = PROTO_RELATIVE; 02357 } 02358 02359 $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL( '', false, $proto ) ) ); 02360 $link = $this->msg( 'returnto' )->rawParams( 02361 Linker::link( $title, $text, array(), $query, $options ) )->escaped(); 02362 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" ); 02363 } 02364 02373 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) { 02374 if ( $returnto == null ) { 02375 $returnto = $this->getRequest()->getText( 'returnto' ); 02376 } 02377 02378 if ( $returntoquery == null ) { 02379 $returntoquery = $this->getRequest()->getText( 'returntoquery' ); 02380 } 02381 02382 if ( $returnto === '' ) { 02383 $returnto = Title::newMainPage(); 02384 } 02385 02386 if ( is_object( $returnto ) ) { 02387 $titleObj = $returnto; 02388 } else { 02389 $titleObj = Title::newFromText( $returnto ); 02390 } 02391 if ( !is_object( $titleObj ) ) { 02392 $titleObj = Title::newMainPage(); 02393 } 02394 02395 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) ); 02396 } 02397 02403 public function headElement( Skin $sk, $includeStyle = true ) { 02404 global $wgContLang; 02405 02406 $userdir = $this->getLanguage()->getDir(); 02407 $sitedir = $wgContLang->getDir(); 02408 02409 if ( $sk->commonPrintStylesheet() ) { 02410 $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' ); 02411 } 02412 02413 $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) ); 02414 02415 if ( $this->getHTMLTitle() == '' ) { 02416 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) ); 02417 } 02418 02419 $openHead = Html::openElement( 'head' ); 02420 if ( $openHead ) { 02421 # Don't bother with the newline if $head == '' 02422 $ret .= "$openHead\n"; 02423 } 02424 02425 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n"; 02426 02427 $ret .= implode( "\n", array( 02428 $this->getHeadLinks( null, true ), 02429 $this->buildCssLinks(), 02430 $this->getHeadScripts(), 02431 $this->getHeadItems() 02432 ) ); 02433 02434 $closeHead = Html::closeElement( 'head' ); 02435 if ( $closeHead ) { 02436 $ret .= "$closeHead\n"; 02437 } 02438 02439 $bodyAttrs = array(); 02440 02441 # Classes for LTR/RTL directionality support 02442 $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir"; 02443 02444 if ( $this->getLanguage()->capitalizeAllNouns() ) { 02445 # A <body> class is probably not the best way to do this . . . 02446 $bodyAttrs['class'] .= ' capitalize-all-nouns'; 02447 } 02448 $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() ); 02449 $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() ); 02450 $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) ); 02451 02452 $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need 02453 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) ); 02454 02455 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n"; 02456 02457 return $ret; 02458 } 02459 02463 private function addDefaultModules() { 02464 global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax, 02465 $wgAjaxWatch, $wgResponsiveImages; 02466 02467 // Add base resources 02468 $this->addModules( array( 02469 'mediawiki.user', 02470 'mediawiki.page.startup', 02471 'mediawiki.page.ready', 02472 ) ); 02473 if ( $wgIncludeLegacyJavaScript ){ 02474 $this->addModules( 'mediawiki.legacy.wikibits' ); 02475 } 02476 02477 if ( $wgPreloadJavaScriptMwUtil ) { 02478 $this->addModules( 'mediawiki.util' ); 02479 } 02480 02481 MWDebug::addModules( $this ); 02482 02483 // Add various resources if required 02484 if ( $wgUseAjax ) { 02485 $this->addModules( 'mediawiki.legacy.ajax' ); 02486 02487 wfRunHooks( 'AjaxAddScript', array( &$this ) ); 02488 02489 if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) { 02490 $this->addModules( 'mediawiki.page.watch.ajax' ); 02491 } 02492 02493 if ( !$this->getUser()->getOption( 'disablesuggest', false ) ) { 02494 $this->addModules( 'mediawiki.searchSuggest' ); 02495 } 02496 } 02497 02498 if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) { 02499 $this->addModules( 'mediawiki.action.view.rightClickEdit' ); 02500 } 02501 02502 # Crazy edit-on-double-click stuff 02503 if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) { 02504 $this->addModules( 'mediawiki.action.view.dblClickEdit' ); 02505 } 02506 02507 // Support for high-density display images 02508 if ( $wgResponsiveImages ) { 02509 $this->addModules( 'mediawiki.hidpi' ); 02510 } 02511 } 02512 02518 public function getResourceLoader() { 02519 if ( is_null( $this->mResourceLoader ) ) { 02520 $this->mResourceLoader = new ResourceLoader(); 02521 } 02522 return $this->mResourceLoader; 02523 } 02524 02534 protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) { 02535 global $wgResourceLoaderUseESI; 02536 02537 $modules = (array) $modules; 02538 02539 if ( !count( $modules ) ) { 02540 return ''; 02541 } 02542 02543 if ( count( $modules ) > 1 ) { 02544 // Remove duplicate module requests 02545 $modules = array_unique( $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 ( $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 => $grpModules ) { 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 // Extract modules that know they're empty 02606 $emptyModules = array (); 02607 foreach ( $grpModules as $key => $module ) { 02608 if ( $module->isKnownEmpty( $context ) ) { 02609 $emptyModules[$key] = 'ready'; 02610 unset( $grpModules[$key] ); 02611 } 02612 } 02613 // Inline empty modules: since they're empty, just mark them as 'ready' 02614 if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) { 02615 // If we're only getting the styles, we don't need to do anything for empty modules. 02616 $links .= Html::inlineScript( 02617 02618 ResourceLoader::makeLoaderConditionalScript( 02619 02620 ResourceLoader::makeLoaderStateScript( $emptyModules ) 02621 02622 ) 02623 02624 ) . "\n"; 02625 } 02626 02627 // If there are no modules left, skip this group 02628 if ( count( $grpModules ) === 0 ) { 02629 continue; 02630 } 02631 02632 // Inline private modules. These can't be loaded through load.php for security 02633 // reasons, see bug 34907. Note that these modules should be loaded from 02634 // getHeadScripts() before the first loader call. Otherwise other modules can't 02635 // properly use them as dependencies (bug 30914) 02636 if ( $group === 'private' ) { 02637 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02638 $links .= Html::inlineStyle( 02639 $resourceLoader->makeModuleResponse( $context, $grpModules ) 02640 ); 02641 } else { 02642 $links .= Html::inlineScript( 02643 ResourceLoader::makeLoaderConditionalScript( 02644 $resourceLoader->makeModuleResponse( $context, $grpModules ) 02645 ) 02646 ); 02647 } 02648 $links .= "\n"; 02649 continue; 02650 } 02651 // Special handling for the user group; because users might change their stuff 02652 // on-wiki like user pages, or user preferences; we need to find the highest 02653 // timestamp of these user-changable modules so we can ensure cache misses on change 02654 // This should NOT be done for the site group (bug 27564) because anons get that too 02655 // and we shouldn't be putting timestamps in Squid-cached HTML 02656 $version = null; 02657 if ( $group === 'user' ) { 02658 // Get the maximum timestamp 02659 $timestamp = 1; 02660 foreach ( $grpModules as $module ) { 02661 $timestamp = max( $timestamp, $module->getModifiedTime( $context ) ); 02662 } 02663 // Add a version parameter so cache will break when things change 02664 $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp ); 02665 } 02666 02667 $url = ResourceLoader::makeLoaderURL( 02668 array_keys( $grpModules ), 02669 $this->getLanguage()->getCode(), 02670 $this->getSkin()->getSkinName(), 02671 $user, 02672 $version, 02673 ResourceLoader::inDebugMode(), 02674 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02675 $this->isPrintable(), 02676 $this->getRequest()->getBool( 'handheld' ), 02677 $extraQuery 02678 ); 02679 if ( $useESI && $wgResourceLoaderUseESI ) { 02680 $esi = Xml::element( 'esi:include', array( 'src' => $url ) ); 02681 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02682 $link = Html::inlineStyle( $esi ); 02683 } else { 02684 $link = Html::inlineScript( $esi ); 02685 } 02686 } else { 02687 // Automatically select style/script elements 02688 if ( $only === ResourceLoaderModule::TYPE_STYLES ) { 02689 $link = Html::linkedStyle( $url ); 02690 } else if ( $loadCall ) { 02691 $link = Html::inlineScript( 02692 ResourceLoader::makeLoaderConditionalScript( 02693 Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) ) 02694 ) 02695 ); 02696 } else { 02697 $link = Html::linkedScript( $url ); 02698 } 02699 } 02700 02701 if( $group == 'noscript' ){ 02702 $links .= Html::rawElement( 'noscript', array(), $link ) . "\n"; 02703 } else { 02704 $links .= $link . "\n"; 02705 } 02706 } 02707 return $links; 02708 } 02709 02716 function getHeadScripts() { 02717 global $wgResourceLoaderExperimentalAsyncLoading; 02718 02719 // Startup - this will immediately load jquery and mediawiki modules 02720 $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true ); 02721 02722 // Load config before anything else 02723 $scripts .= Html::inlineScript( 02724 ResourceLoader::makeLoaderConditionalScript( 02725 ResourceLoader::makeConfigSetScript( $this->getJSVars() ) 02726 ) 02727 ); 02728 02729 // Load embeddable private modules before any loader links 02730 // This needs to be TYPE_COMBINED so these modules are properly wrapped 02731 // in mw.loader.implement() calls and deferred until mw.user is available 02732 $embedScripts = array( 'user.options', 'user.tokens' ); 02733 $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED ); 02734 02735 // Script and Messages "only" requests marked for top inclusion 02736 // Messages should go first 02737 $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES ); 02738 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS ); 02739 02740 // Modules requests - let the client calculate dependencies and batch requests as it likes 02741 // Only load modules that have marked themselves for loading at the top 02742 $modules = $this->getModules( true, 'top' ); 02743 if ( $modules ) { 02744 $scripts .= Html::inlineScript( 02745 ResourceLoader::makeLoaderConditionalScript( 02746 Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) 02747 ) 02748 ); 02749 } 02750 02751 if ( $wgResourceLoaderExperimentalAsyncLoading ) { 02752 $scripts .= $this->getScriptsForBottomQueue( true ); 02753 } 02754 02755 return $scripts; 02756 } 02757 02767 function getScriptsForBottomQueue( $inHead ) { 02768 global $wgUseSiteJs, $wgAllowUserJs; 02769 02770 // Script and Messages "only" requests marked for bottom inclusion 02771 // If we're in the <head>, use load() calls rather than <script src="..."> tags 02772 // Messages should go first 02773 $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), 02774 ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(), 02775 /* $loadCall = */ $inHead 02776 ); 02777 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), 02778 ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(), 02779 /* $loadCall = */ $inHead 02780 ); 02781 02782 // Modules requests - let the client calculate dependencies and batch requests as it likes 02783 // Only load modules that have marked themselves for loading at the bottom 02784 $modules = $this->getModules( true, 'bottom' ); 02785 if ( $modules ) { 02786 $scripts .= Html::inlineScript( 02787 ResourceLoader::makeLoaderConditionalScript( 02788 Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) ) 02789 ) 02790 ); 02791 } 02792 02793 // Legacy Scripts 02794 $scripts .= "\n" . $this->mScripts; 02795 02796 $defaultModules = array(); 02797 02798 // Add site JS if enabled 02799 if ( $wgUseSiteJs ) { 02800 $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS, 02801 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02802 ); 02803 $defaultModules['site'] = 'loading'; 02804 } else { 02805 // The wiki is configured to not allow a site module. 02806 $defaultModules['site'] = 'missing'; 02807 } 02808 02809 // Add user JS if enabled 02810 if ( $wgAllowUserJs ) { 02811 if ( $this->getUser()->isLoggedIn() ) { 02812 if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) { 02813 # XXX: additional security check/prompt? 02814 // We're on a preview of a JS subpage 02815 // Exclude this page from the user module in case it's in there (bug 26283) 02816 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false, 02817 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead 02818 ); 02819 // Load the previewed JS 02820 $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n"; 02821 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded 02822 // asynchronously and may arrive *after* the inline script here. So the previewed code 02823 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js... 02824 } else { 02825 // Include the user module normally, i.e., raw to avoid it being wrapped in a closure. 02826 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, 02827 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02828 ); 02829 } 02830 $defaultModules['user'] = 'loading'; 02831 } else { 02832 // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid 02833 // blocking default gadgets that might depend on it. Although arguably default-enabled 02834 // gadgets should not depend on the user module, it's harmless and less error-prone to 02835 // handle this case. 02836 $defaultModules['user'] = 'ready'; 02837 } 02838 } else { 02839 // User JS disabled 02840 $defaultModules['user'] = 'missing'; 02841 } 02842 02843 // Group JS is only enabled if site JS is enabled. 02844 if ( $wgUseSiteJs ) { 02845 if ( $this->getUser()->isLoggedIn() ) { 02846 $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED, 02847 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02848 ); 02849 $defaultModules['user.groups'] = 'loading'; 02850 } else { 02851 // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to 02852 // avoid blocking gadgets that might depend upon the module. 02853 $defaultModules['user.groups'] = 'ready'; 02854 } 02855 } else { 02856 // Site (and group JS) disabled 02857 $defaultModules['user.groups'] = 'missing'; 02858 } 02859 02860 $loaderInit = ''; 02861 if ( $inHead ) { 02862 // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'. 02863 foreach ( $defaultModules as $m => $state ) { 02864 if ( $state == 'loading' ) { 02865 unset( $defaultModules[$m] ); 02866 } 02867 } 02868 } 02869 if ( count( $defaultModules ) > 0 ) { 02870 $loaderInit = Html::inlineScript( 02871 ResourceLoader::makeLoaderConditionalScript( 02872 ResourceLoader::makeLoaderStateScript( $defaultModules ) 02873 ) 02874 ) . "\n"; 02875 } 02876 return $loaderInit . $scripts; 02877 } 02878 02883 function getBottomScripts() { 02884 global $wgResourceLoaderExperimentalAsyncLoading; 02885 if ( !$wgResourceLoaderExperimentalAsyncLoading ) { 02886 return $this->getScriptsForBottomQueue( false ); 02887 } else { 02888 return ''; 02889 } 02890 } 02891 02898 public function addJsConfigVars( $keys, $value = null ) { 02899 if ( is_array( $keys ) ) { 02900 foreach ( $keys as $key => $value ) { 02901 $this->mJsConfigVars[$key] = $value; 02902 } 02903 return; 02904 } 02905 02906 $this->mJsConfigVars[$keys] = $value; 02907 } 02908 02909 02922 public function getJSVars() { 02923 global $wgContLang; 02924 02925 $latestRevID = 0; 02926 $pageID = 0; 02927 $canonicalName = false; # bug 21115 02928 02929 $title = $this->getTitle(); 02930 $ns = $title->getNamespace(); 02931 $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText(); 02932 02933 // Get the relevant title so that AJAX features can use the correct page name 02934 // when making API requests from certain special pages (bug 34972). 02935 $relevantTitle = $this->getSkin()->getRelevantTitle(); 02936 02937 if ( $ns == NS_SPECIAL ) { 02938 list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); 02939 } elseif ( $this->canUseWikiPage() ) { 02940 $wikiPage = $this->getWikiPage(); 02941 $latestRevID = $wikiPage->getLatest(); 02942 $pageID = $wikiPage->getId(); 02943 } 02944 02945 $lang = $title->getPageLanguage(); 02946 02947 // Pre-process information 02948 $separatorTransTable = $lang->separatorTransformTable(); 02949 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); 02950 $compactSeparatorTransTable = array( 02951 implode( "\t", array_keys( $separatorTransTable ) ), 02952 implode( "\t", $separatorTransTable ), 02953 ); 02954 $digitTransTable = $lang->digitTransformTable(); 02955 $digitTransTable = $digitTransTable ? $digitTransTable : array(); 02956 $compactDigitTransTable = array( 02957 implode( "\t", array_keys( $digitTransTable ) ), 02958 implode( "\t", $digitTransTable ), 02959 ); 02960 02961 $vars = array( 02962 'wgCanonicalNamespace' => $nsname, 02963 'wgCanonicalSpecialPageName' => $canonicalName, 02964 'wgNamespaceNumber' => $title->getNamespace(), 02965 'wgPageName' => $title->getPrefixedDBKey(), 02966 'wgTitle' => $title->getText(), 02967 'wgCurRevisionId' => $latestRevID, 02968 'wgArticleId' => $pageID, 02969 'wgIsArticle' => $this->isArticle(), 02970 'wgAction' => Action::getActionName( $this->getContext() ), 02971 'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(), 02972 'wgUserGroups' => $this->getUser()->getEffectiveGroups(), 02973 'wgCategories' => $this->getCategories(), 02974 'wgBreakFrames' => $this->getFrameOptions() == 'DENY', 02975 'wgPageContentLanguage' => $lang->getCode(), 02976 'wgSeparatorTransformTable' => $compactSeparatorTransTable, 02977 'wgDigitTransformTable' => $compactDigitTransTable, 02978 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(), 02979 'wgMonthNames' => $lang->getMonthNamesArray(), 02980 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(), 02981 'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(), 02982 ); 02983 if ( $wgContLang->hasVariants() ) { 02984 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); 02985 } 02986 foreach ( $title->getRestrictionTypes() as $type ) { 02987 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type ); 02988 } 02989 if ( $title->isMainPage() ) { 02990 $vars['wgIsMainPage'] = true; 02991 } 02992 if ( $this->mRedirectedFrom ) { 02993 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey(); 02994 } 02995 02996 // Allow extensions to add their custom variables to the mw.config map. 02997 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not 02998 // page-dependant but site-wide (without state). 02999 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. 03000 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) ); 03001 03002 // Merge in variables from addJsConfigVars last 03003 return array_merge( $vars, $this->mJsConfigVars ); 03004 } 03005 03015 public function userCanPreview() { 03016 if ( $this->getRequest()->getVal( 'action' ) != 'submit' 03017 || !$this->getRequest()->wasPosted() 03018 || !$this->getUser()->matchEditToken( 03019 $this->getRequest()->getVal( 'wpEditToken' ) ) 03020 ) { 03021 return false; 03022 } 03023 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) { 03024 return false; 03025 } 03026 03027 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) ); 03028 } 03029 03035 public function getHeadLinksArray( $addContentType = false ) { 03036 global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI, 03037 $wgSitename, $wgVersion, $wgHtml5, $wgMimeType, 03038 $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes, 03039 $wgDisableLangConversion, $wgCanonicalLanguageLinks, 03040 $wgRightsPage, $wgRightsUrl; 03041 03042 $tags = array(); 03043 03044 if ( $addContentType ) { 03045 if ( $wgHtml5 ) { 03046 # More succinct than <meta http-equiv=Content-Type>, has the 03047 # same effect 03048 $tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) ); 03049 } else { 03050 $tags['meta-content-type'] = Html::element( 'meta', array( 03051 'http-equiv' => 'Content-Type', 03052 'content' => "$wgMimeType; charset=UTF-8" 03053 ) ); 03054 $tags['meta-content-style-type'] = Html::element( 'meta', array( // bug 15835 03055 'http-equiv' => 'Content-Style-Type', 03056 'content' => 'text/css' 03057 ) ); 03058 } 03059 } 03060 03061 $tags['meta-generator'] = Html::element( 'meta', array( 03062 'name' => 'generator', 03063 'content' => "MediaWiki $wgVersion", 03064 ) ); 03065 03066 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}"; 03067 if( $p !== 'index,follow' ) { 03068 // http://www.robotstxt.org/wc/meta-user.html 03069 // Only show if it's different from the default robots policy 03070 $tags['meta-robots'] = Html::element( 'meta', array( 03071 'name' => 'robots', 03072 'content' => $p, 03073 ) ); 03074 } 03075 03076 if ( count( $this->mKeywords ) > 0 ) { 03077 $strip = array( 03078 "/<.*?" . ">/" => '', 03079 "/_/" => ' ' 03080 ); 03081 $tags['meta-keywords'] = Html::element( 'meta', array( 03082 'name' => 'keywords', 03083 'content' => preg_replace( 03084 array_keys( $strip ), 03085 array_values( $strip ), 03086 implode( ',', $this->mKeywords ) 03087 ) 03088 ) ); 03089 } 03090 03091 foreach ( $this->mMetatags as $tag ) { 03092 if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) { 03093 $a = 'http-equiv'; 03094 $tag[0] = substr( $tag[0], 5 ); 03095 } else { 03096 $a = 'name'; 03097 } 03098 $tagName = "meta-{$tag[0]}"; 03099 if ( isset( $tags[$tagName] ) ) { 03100 $tagName .= $tag[1]; 03101 } 03102 $tags[$tagName] = Html::element( 'meta', 03103 array( 03104 $a => $tag[0], 03105 'content' => $tag[1] 03106 ) 03107 ); 03108 } 03109 03110 foreach ( $this->mLinktags as $tag ) { 03111 $tags[] = Html::element( 'link', $tag ); 03112 } 03113 03114 # Universal edit button 03115 if ( $wgUniversalEditButton && $this->isArticleRelated() ) { 03116 $user = $this->getUser(); 03117 if ( $this->getTitle()->quickUserCan( 'edit', $user ) 03118 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) { 03119 // Original UniversalEditButton 03120 $msg = $this->msg( 'edit' )->text(); 03121 $tags['universal-edit-button'] = Html::element( 'link', array( 03122 'rel' => 'alternate', 03123 'type' => 'application/x-wiki', 03124 'title' => $msg, 03125 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03126 ) ); 03127 // Alternate edit link 03128 $tags['alternative-edit'] = Html::element( 'link', array( 03129 'rel' => 'edit', 03130 'title' => $msg, 03131 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03132 ) ); 03133 } 03134 } 03135 03136 # Generally the order of the favicon and apple-touch-icon links 03137 # should not matter, but Konqueror (3.5.9 at least) incorrectly 03138 # uses whichever one appears later in the HTML source. Make sure 03139 # apple-touch-icon is specified first to avoid this. 03140 if ( $wgAppleTouchIcon !== false ) { 03141 $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) ); 03142 } 03143 03144 if ( $wgFavicon !== false ) { 03145 $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) ); 03146 } 03147 03148 # OpenSearch description link 03149 $tags['opensearch'] = Html::element( 'link', array( 03150 'rel' => 'search', 03151 'type' => 'application/opensearchdescription+xml', 03152 'href' => wfScript( 'opensearch_desc' ), 03153 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(), 03154 ) ); 03155 03156 if ( $wgEnableAPI ) { 03157 # Real Simple Discovery link, provides auto-discovery information 03158 # for the MediaWiki API (and potentially additional custom API 03159 # support such as WordPress or Twitter-compatible APIs for a 03160 # blogging extension, etc) 03161 $tags['rsd'] = Html::element( 'link', array( 03162 'rel' => 'EditURI', 03163 'type' => 'application/rsd+xml', 03164 // Output a protocol-relative URL here if $wgServer is protocol-relative 03165 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though 03166 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ), 03167 ) ); 03168 } 03169 03170 03171 # Language variants 03172 if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) { 03173 $lang = $this->getTitle()->getPageLanguage(); 03174 if ( $lang->hasVariants() ) { 03175 03176 $urlvar = $lang->getURLVariant(); 03177 03178 if ( !$urlvar ) { 03179 $variants = $lang->getVariants(); 03180 foreach ( $variants as $_v ) { 03181 $tags["variant-$_v"] = Html::element( 'link', array( 03182 'rel' => 'alternate', 03183 'hreflang' => $_v, 03184 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) ) 03185 ); 03186 } 03187 } else { 03188 $tags['canonical'] = Html::element( 'link', array( 03189 'rel' => 'canonical', 03190 'href' => $this->getTitle()->getCanonicalUrl() 03191 ) ); 03192 } 03193 } 03194 } 03195 03196 # Copyright 03197 $copyright = ''; 03198 if ( $wgRightsPage ) { 03199 $copy = Title::newFromText( $wgRightsPage ); 03200 03201 if ( $copy ) { 03202 $copyright = $copy->getLocalURL(); 03203 } 03204 } 03205 03206 if ( !$copyright && $wgRightsUrl ) { 03207 $copyright = $wgRightsUrl; 03208 } 03209 03210 if ( $copyright ) { 03211 $tags['copyright'] = Html::element( 'link', array( 03212 'rel' => 'copyright', 03213 'href' => $copyright ) 03214 ); 03215 } 03216 03217 # Feeds 03218 if ( $wgFeed ) { 03219 foreach( $this->getSyndicationLinks() as $format => $link ) { 03220 # Use the page name for the title. In principle, this could 03221 # lead to issues with having the same name for different feeds 03222 # corresponding to the same page, but we can't avoid that at 03223 # this low a level. 03224 03225 $tags[] = $this->feedLink( 03226 $format, 03227 $link, 03228 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep) 03229 $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text() 03230 ); 03231 } 03232 03233 # Recent changes feed should appear on every page (except recentchanges, 03234 # that would be redundant). Put it after the per-page feed to avoid 03235 # changing existing behavior. It's still available, probably via a 03236 # menu in your browser. Some sites might have a different feed they'd 03237 # like to promote instead of the RC feed (maybe like a "Recent New Articles" 03238 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined. 03239 # If so, use it instead. 03240 if ( $wgOverrideSiteFeed ) { 03241 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) { 03242 // Note, this->feedLink escapes the url. 03243 $tags[] = $this->feedLink( 03244 $type, 03245 $feedUrl, 03246 $this->msg( "site-{$type}-feed", $wgSitename )->text() 03247 ); 03248 } 03249 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) { 03250 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' ); 03251 foreach ( $wgAdvertisedFeedTypes as $format ) { 03252 $tags[] = $this->feedLink( 03253 $format, 03254 $rctitle->getLocalURL( "feed={$format}" ), 03255 $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'. 03256 ); 03257 } 03258 } 03259 } 03260 return $tags; 03261 } 03262 03269 public function getHeadLinks( $unused = null, $addContentType = false ) { 03270 return implode( "\n", $this->getHeadLinksArray( $addContentType ) ); 03271 } 03272 03281 private function feedLink( $type, $url, $text ) { 03282 return Html::element( 'link', array( 03283 'rel' => 'alternate', 03284 'type' => "application/$type+xml", 03285 'title' => $text, 03286 'href' => $url ) 03287 ); 03288 } 03289 03299 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) { 03300 $options = array(); 03301 // Even though we expect the media type to be lowercase, but here we 03302 // force it to lowercase to be safe. 03303 if( $media ) { 03304 $options['media'] = $media; 03305 } 03306 if( $condition ) { 03307 $options['condition'] = $condition; 03308 } 03309 if( $dir ) { 03310 $options['dir'] = $dir; 03311 } 03312 $this->styles[$style] = $options; 03313 } 03314 03320 public function addInlineStyle( $style_css, $flip = 'noflip' ) { 03321 if( $flip === 'flip' && $this->getLanguage()->isRTL() ) { 03322 # If wanted, and the interface is right-to-left, flip the CSS 03323 $style_css = CSSJanus::transform( $style_css, true, false ); 03324 } 03325 $this->mInlineStyles .= Html::inlineStyle( $style_css ); 03326 } 03327 03334 public function buildCssLinks() { 03335 global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, 03336 $wgLang, $wgContLang; 03337 03338 $this->getSkin()->setupSkinUserCss( $this ); 03339 03340 // Add ResourceLoader styles 03341 // Split the styles into four groups 03342 $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() ); 03343 $otherTags = ''; // Tags to append after the normal <link> tags 03344 $resourceLoader = $this->getResourceLoader(); 03345 03346 $moduleStyles = $this->getModuleStyles(); 03347 03348 // Per-site custom styles 03349 if ( $wgUseSiteCss ) { 03350 $moduleStyles[] = 'site'; 03351 $moduleStyles[] = 'noscript'; 03352 if( $this->getUser()->isLoggedIn() ){ 03353 $moduleStyles[] = 'user.groups'; 03354 } 03355 } 03356 03357 // Per-user custom styles 03358 if ( $wgAllowUserCss ) { 03359 if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) { 03360 // We're on a preview of a CSS subpage 03361 // Exclude this page from the user module in case it's in there (bug 26283) 03362 $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false, 03363 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ) 03364 ); 03365 03366 // Load the previewed CSS 03367 // If needed, Janus it first. This is user-supplied CSS, so it's 03368 // assumed to be right for the content language directionality. 03369 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' ); 03370 if ( $wgLang->getDir() !== $wgContLang->getDir() ) { 03371 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false ); 03372 } 03373 $otherTags .= Html::inlineStyle( $previewedCSS ); 03374 } else { 03375 // Load the user styles normally 03376 $moduleStyles[] = 'user'; 03377 } 03378 } 03379 03380 // Per-user preference styles 03381 if ( $wgAllowUserCssPrefs ) { 03382 $moduleStyles[] = 'user.cssprefs'; 03383 } 03384 03385 foreach ( $moduleStyles as $name ) { 03386 $module = $resourceLoader->getModule( $name ); 03387 if ( !$module ) { 03388 continue; 03389 } 03390 $group = $module->getGroup(); 03391 // Modules in groups named "other" or anything different than "user", "site" or "private" 03392 // will be placed in the "other" group 03393 $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name; 03394 } 03395 03396 // We want site, private and user styles to override dynamically added styles from modules, but we want 03397 // dynamically added styles to override statically added styles from other modules. So the order 03398 // has to be other, dynamic, site, private, user 03399 // Add statically added styles for other modules 03400 $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES ); 03401 // Add normal styles added through addStyle()/addInlineStyle() here 03402 $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles; 03403 // Add marker tag to mark the place where the client-side loader should inject dynamic styles 03404 // We use a <meta> tag with a made-up name for this because that's valid HTML 03405 $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n"; 03406 03407 // Add site, private and user styles 03408 // 'private' at present only contains user.options, so put that before 'user' 03409 // Any future private modules will likely have a similar user-specific character 03410 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) { 03411 $ret .= $this->makeResourceLoaderLink( $styles[$group], 03412 ResourceLoaderModule::TYPE_STYLES 03413 ); 03414 } 03415 03416 // Add stuff in $otherTags (previewed user CSS if applicable) 03417 $ret .= $otherTags; 03418 return $ret; 03419 } 03420 03424 public function buildCssLinksArray() { 03425 $links = array(); 03426 03427 // Add any extension CSS 03428 foreach ( $this->mExtStyles as $url ) { 03429 $this->addStyle( $url ); 03430 } 03431 $this->mExtStyles = array(); 03432 03433 foreach( $this->styles as $file => $options ) { 03434 $link = $this->styleLink( $file, $options ); 03435 if( $link ) { 03436 $links[$file] = $link; 03437 } 03438 } 03439 return $links; 03440 } 03441 03450 protected function styleLink( $style, $options ) { 03451 if( isset( $options['dir'] ) ) { 03452 if( $this->getLanguage()->getDir() != $options['dir'] ) { 03453 return ''; 03454 } 03455 } 03456 03457 if( isset( $options['media'] ) ) { 03458 $media = self::transformCssMedia( $options['media'] ); 03459 if( is_null( $media ) ) { 03460 return ''; 03461 } 03462 } else { 03463 $media = 'all'; 03464 } 03465 03466 if( substr( $style, 0, 1 ) == '/' || 03467 substr( $style, 0, 5 ) == 'http:' || 03468 substr( $style, 0, 6 ) == 'https:' ) { 03469 $url = $style; 03470 } else { 03471 global $wgStylePath, $wgStyleVersion; 03472 $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion; 03473 } 03474 03475 $link = Html::linkedStyle( $url, $media ); 03476 03477 if( isset( $options['condition'] ) ) { 03478 $condition = htmlspecialchars( $options['condition'] ); 03479 $link = "<!--[if $condition]>$link<![endif]-->"; 03480 } 03481 return $link; 03482 } 03483 03490 public static function transformCssMedia( $media ) { 03491 global $wgRequest, $wgHandheldForIPhone; 03492 03493 // Switch in on-screen display for media testing 03494 $switches = array( 03495 'printable' => 'print', 03496 'handheld' => 'handheld', 03497 ); 03498 foreach( $switches as $switch => $targetMedia ) { 03499 if( $wgRequest->getBool( $switch ) ) { 03500 if( $media == $targetMedia ) { 03501 $media = ''; 03502 } elseif( $media == 'screen' ) { 03503 return null; 03504 } 03505 } 03506 } 03507 03508 // Expand longer media queries as iPhone doesn't grok 'handheld' 03509 if( $wgHandheldForIPhone ) { 03510 $mediaAliases = array( 03511 'screen' => 'screen and (min-device-width: 481px)', 03512 'handheld' => 'handheld, only screen and (max-device-width: 480px)', 03513 ); 03514 03515 if( isset( $mediaAliases[$media] ) ) { 03516 $media = $mediaAliases[$media]; 03517 } 03518 } 03519 03520 return $media; 03521 } 03522 03529 public function addWikiMsg( /*...*/ ) { 03530 $args = func_get_args(); 03531 $name = array_shift( $args ); 03532 $this->addWikiMsgArray( $name, $args ); 03533 } 03534 03543 public function addWikiMsgArray( $name, $args ) { 03544 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() ); 03545 } 03546 03570 public function wrapWikiMsg( $wrap /*, ...*/ ) { 03571 $msgSpecs = func_get_args(); 03572 array_shift( $msgSpecs ); 03573 $msgSpecs = array_values( $msgSpecs ); 03574 $s = $wrap; 03575 foreach ( $msgSpecs as $n => $spec ) { 03576 if ( is_array( $spec ) ) { 03577 $args = $spec; 03578 $name = array_shift( $args ); 03579 if ( isset( $args['options'] ) ) { 03580 unset( $args['options'] ); 03581 wfDeprecated( 03582 'Adding "options" to ' . __METHOD__ . ' is no longer supported', 03583 '1.20' 03584 ); 03585 } 03586 } else { 03587 $args = array(); 03588 $name = $spec; 03589 } 03590 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s ); 03591 } 03592 $this->addWikiText( $s ); 03593 } 03594 03604 public function includeJQuery( $modules = array() ) { 03605 return array(); 03606 } 03607 03608 }