MediaWiki
REL1_23
|
00001 <?php 00038 class OutputPage extends ContextSource { 00040 var $mMetatags = array(); 00041 00042 var $mLinktags = array(); 00043 var $mCanonicalUrl = false; 00044 00046 var $mExtStyles = array(); 00047 00049 var $mPagetitle = ''; 00050 00052 var $mBodytext = ''; 00053 00059 public $mDebugtext = ''; 00060 00062 var $mHTMLtitle = ''; 00063 00065 var $mIsarticle = false; 00066 00071 var $mIsArticleRelated = true; 00072 00077 var $mPrintable = false; 00078 00085 private $mSubtitle = array(); 00086 00087 var $mRedirect = ''; 00088 var $mStatusCode; 00089 00094 var $mLastModified = ''; 00095 00106 var $mETag = false; 00107 00108 var $mCategoryLinks = array(); 00109 var $mCategories = array(); 00110 00112 var $mLanguageLinks = array(); 00113 00120 var $mScripts = ''; 00121 00125 var $mInlineStyles = ''; 00126 00127 // 00128 var $mLinkColours; 00129 00134 var $mPageLinkTitle = ''; 00135 00137 var $mHeadItems = array(); 00138 00139 // @todo FIXME: Next variables probably comes from the resource loader 00140 var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array(); 00141 var $mResourceLoader; 00142 var $mJsConfigVars = array(); 00143 00145 var $mInlineMsg = array(); 00146 00147 var $mTemplateIds = array(); 00148 var $mImageTimeKeys = array(); 00149 00150 var $mRedirectCode = ''; 00151 00152 var $mFeedLinksAppendQuery = null; 00153 00159 protected $mAllowedModuleOrigin = ResourceLoaderModule::ORIGIN_ALL; 00160 00166 var $mDoNothing = false; 00167 00168 // Parser related. 00169 var $mContainsOldMagic = 0, $mContainsNewMagic = 0; 00170 00175 protected $mParserOptions = null; 00176 00183 var $mFeedLinks = array(); 00184 00185 // Gwicke work on squid caching? Roughly from 2003. 00186 var $mEnableClientCache = true; 00187 00192 var $mArticleBodyOnly = false; 00193 00194 var $mNewSectionLink = false; 00195 var $mHideNewSectionLink = false; 00196 00202 var $mNoGallery = false; 00203 00204 // should be private. 00205 var $mPageTitleActionText = ''; 00206 var $mParseWarnings = array(); 00207 00208 // Cache stuff. Looks like mEnableClientCache 00209 var $mSquidMaxage = 0; 00210 00211 // @todo document 00212 var $mPreventClickjacking = true; 00213 00215 var $mRevisionId = null; 00216 private $mRevisionTimestamp = null; 00217 00218 var $mFileVersion = null; 00219 00228 var $styles = array(); 00229 00233 protected $mJQueryDone = false; 00234 00235 private $mIndexPolicy = 'index'; 00236 private $mFollowPolicy = 'follow'; 00237 private $mVaryHeader = array( 00238 'Accept-Encoding' => array( 'list-contains=gzip' ), 00239 ); 00240 00247 private $mRedirectedFrom = null; 00248 00252 private $mProperties = array(); 00253 00257 private $mTarget = null; 00258 00262 private $mEnableTOC = true; 00263 00267 private $mEnableSectionEditLinks = true; 00268 00274 function __construct( IContextSource $context = null ) { 00275 if ( $context === null ) { 00276 # Extensions should use `new RequestContext` instead of `new OutputPage` now. 00277 wfDeprecated( __METHOD__, '1.18' ); 00278 } else { 00279 $this->setContext( $context ); 00280 } 00281 } 00282 00289 public function redirect( $url, $responsecode = '302' ) { 00290 # Strip newlines as a paranoia check for header injection in PHP<5.1.2 00291 $this->mRedirect = str_replace( "\n", '', $url ); 00292 $this->mRedirectCode = $responsecode; 00293 } 00294 00300 public function getRedirect() { 00301 return $this->mRedirect; 00302 } 00303 00309 public function setStatusCode( $statusCode ) { 00310 $this->mStatusCode = $statusCode; 00311 } 00312 00320 function addMeta( $name, $val ) { 00321 array_push( $this->mMetatags, array( $name, $val ) ); 00322 } 00323 00331 function addLink( $linkarr ) { 00332 array_push( $this->mLinktags, $linkarr ); 00333 } 00334 00342 function addMetadataLink( $linkarr ) { 00343 $linkarr['rel'] = $this->getMetadataAttribute(); 00344 $this->addLink( $linkarr ); 00345 } 00346 00351 function setCanonicalUrl( $url ) { 00352 $this->mCanonicalUrl = $url; 00353 } 00354 00360 public function getMetadataAttribute() { 00361 # note: buggy CC software only reads first "meta" link 00362 static $haveMeta = false; 00363 if ( $haveMeta ) { 00364 return 'alternate meta'; 00365 } else { 00366 $haveMeta = true; 00367 return 'meta'; 00368 } 00369 } 00370 00376 function addScript( $script ) { 00377 $this->mScripts .= $script . "\n"; 00378 } 00379 00388 public function addExtensionStyle( $url ) { 00389 array_push( $this->mExtStyles, $url ); 00390 } 00391 00397 function getExtStyle() { 00398 return $this->mExtStyles; 00399 } 00400 00408 public function addScriptFile( $file, $version = null ) { 00409 global $wgStylePath, $wgStyleVersion; 00410 // See if $file parameter is an absolute URL or begins with a slash 00411 if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) { 00412 $path = $file; 00413 } else { 00414 $path = "{$wgStylePath}/common/{$file}"; 00415 } 00416 if ( is_null( $version ) ) { 00417 $version = $wgStyleVersion; 00418 } 00419 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) ); 00420 } 00421 00427 public function addInlineScript( $script ) { 00428 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n"; 00429 } 00430 00436 function getScript() { 00437 return $this->mScripts . $this->getHeadItems(); 00438 } 00439 00448 protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ) { 00449 $resourceLoader = $this->getResourceLoader(); 00450 $filteredModules = array(); 00451 foreach ( $modules as $val ) { 00452 $module = $resourceLoader->getModule( $val ); 00453 if ( $module instanceof ResourceLoaderModule 00454 && $module->getOrigin() <= $this->getAllowedModules( $type ) 00455 && ( is_null( $position ) || $module->getPosition() == $position ) 00456 && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) ) 00457 ) { 00458 $filteredModules[] = $val; 00459 } 00460 } 00461 return $filteredModules; 00462 } 00463 00472 public function getModules( $filter = false, $position = null, $param = 'mModules' ) { 00473 $modules = array_values( array_unique( $this->$param ) ); 00474 return $filter 00475 ? $this->filterModules( $modules, $position ) 00476 : $modules; 00477 } 00478 00486 public function addModules( $modules ) { 00487 $this->mModules = array_merge( $this->mModules, (array)$modules ); 00488 } 00489 00498 public function getModuleScripts( $filter = false, $position = null ) { 00499 return $this->getModules( $filter, $position, 'mModuleScripts' ); 00500 } 00501 00509 public function addModuleScripts( $modules ) { 00510 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules ); 00511 } 00512 00521 public function getModuleStyles( $filter = false, $position = null ) { 00522 return $this->getModules( $filter, $position, 'mModuleStyles' ); 00523 } 00524 00534 public function addModuleStyles( $modules ) { 00535 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules ); 00536 } 00537 00546 public function getModuleMessages( $filter = false, $position = null ) { 00547 return $this->getModules( $filter, $position, 'mModuleMessages' ); 00548 } 00549 00557 public function addModuleMessages( $modules ) { 00558 $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules ); 00559 } 00560 00564 public function getTarget() { 00565 return $this->mTarget; 00566 } 00567 00573 public function setTarget( $target ) { 00574 $this->mTarget = $target; 00575 } 00576 00582 function getHeadItemsArray() { 00583 return $this->mHeadItems; 00584 } 00585 00591 function getHeadItems() { 00592 $s = ''; 00593 foreach ( $this->mHeadItems as $item ) { 00594 $s .= $item; 00595 } 00596 return $s; 00597 } 00598 00605 public function addHeadItem( $name, $value ) { 00606 $this->mHeadItems[$name] = $value; 00607 } 00608 00615 public function hasHeadItem( $name ) { 00616 return isset( $this->mHeadItems[$name] ); 00617 } 00618 00624 function setETag( $tag ) { 00625 $this->mETag = $tag; 00626 } 00627 00635 public function setArticleBodyOnly( $only ) { 00636 $this->mArticleBodyOnly = $only; 00637 } 00638 00644 public function getArticleBodyOnly() { 00645 return $this->mArticleBodyOnly; 00646 } 00647 00655 public function setProperty( $name, $value ) { 00656 $this->mProperties[$name] = $value; 00657 } 00658 00666 public function getProperty( $name ) { 00667 if ( isset( $this->mProperties[$name] ) ) { 00668 return $this->mProperties[$name]; 00669 } else { 00670 return null; 00671 } 00672 } 00673 00685 public function checkLastModified( $timestamp ) { 00686 global $wgCachePages, $wgCacheEpoch, $wgUseSquid, $wgSquidMaxage; 00687 00688 if ( !$timestamp || $timestamp == '19700101000000' ) { 00689 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" ); 00690 return false; 00691 } 00692 if ( !$wgCachePages ) { 00693 wfDebug( __METHOD__ . ": CACHE DISABLED\n" ); 00694 return false; 00695 } 00696 00697 $timestamp = wfTimestamp( TS_MW, $timestamp ); 00698 $modifiedTimes = array( 00699 'page' => $timestamp, 00700 'user' => $this->getUser()->getTouched(), 00701 'epoch' => $wgCacheEpoch 00702 ); 00703 if ( $wgUseSquid ) { 00704 // bug 44570: the core page itself may not change, but resources might 00705 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $wgSquidMaxage ); 00706 } 00707 wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) ); 00708 00709 $maxModified = max( $modifiedTimes ); 00710 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified ); 00711 00712 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' ); 00713 if ( $clientHeader === false ) { 00714 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", 'log' ); 00715 return false; 00716 } 00717 00718 # IE sends sizes after the date like this: 00719 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 00720 # this breaks strtotime(). 00721 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader ); 00722 00723 wfSuppressWarnings(); // E_STRICT system time bitching 00724 $clientHeaderTime = strtotime( $clientHeader ); 00725 wfRestoreWarnings(); 00726 if ( !$clientHeaderTime ) { 00727 wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" ); 00728 return false; 00729 } 00730 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime ); 00731 00732 # Make debug info 00733 $info = ''; 00734 foreach ( $modifiedTimes as $name => $value ) { 00735 if ( $info !== '' ) { 00736 $info .= ', '; 00737 } 00738 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value ); 00739 } 00740 00741 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " . 00742 wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", 'log' ); 00743 wfDebug( __METHOD__ . ": effective Last-Modified: " . 00744 wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", 'log' ); 00745 if ( $clientHeaderTime < $maxModified ) { 00746 wfDebug( __METHOD__ . ": STALE, $info\n", 'log' ); 00747 return false; 00748 } 00749 00750 # Not modified 00751 # Give a 304 response code and disable body output 00752 wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", 'log' ); 00753 ini_set( 'zlib.output_compression', 0 ); 00754 $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" ); 00755 $this->sendCacheControl(); 00756 $this->disable(); 00757 00758 // Don't output a compressed blob when using ob_gzhandler; 00759 // it's technically against HTTP spec and seems to confuse 00760 // Firefox when the response gets split over two packets. 00761 wfClearOutputBuffers(); 00762 00763 return true; 00764 } 00765 00772 public function setLastModified( $timestamp ) { 00773 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp ); 00774 } 00775 00784 public function setRobotPolicy( $policy ) { 00785 $policy = Article::formatRobotPolicy( $policy ); 00786 00787 if ( isset( $policy['index'] ) ) { 00788 $this->setIndexPolicy( $policy['index'] ); 00789 } 00790 if ( isset( $policy['follow'] ) ) { 00791 $this->setFollowPolicy( $policy['follow'] ); 00792 } 00793 } 00794 00802 public function setIndexPolicy( $policy ) { 00803 $policy = trim( $policy ); 00804 if ( in_array( $policy, array( 'index', 'noindex' ) ) ) { 00805 $this->mIndexPolicy = $policy; 00806 } 00807 } 00808 00816 public function setFollowPolicy( $policy ) { 00817 $policy = trim( $policy ); 00818 if ( in_array( $policy, array( 'follow', 'nofollow' ) ) ) { 00819 $this->mFollowPolicy = $policy; 00820 } 00821 } 00822 00829 public function setPageTitleActionText( $text ) { 00830 $this->mPageTitleActionText = $text; 00831 } 00832 00838 public function getPageTitleActionText() { 00839 if ( isset( $this->mPageTitleActionText ) ) { 00840 return $this->mPageTitleActionText; 00841 } 00842 return ''; 00843 } 00844 00851 public function setHTMLTitle( $name ) { 00852 if ( $name instanceof Message ) { 00853 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text(); 00854 } else { 00855 $this->mHTMLtitle = $name; 00856 } 00857 } 00858 00864 public function getHTMLTitle() { 00865 return $this->mHTMLtitle; 00866 } 00867 00873 public function setRedirectedFrom( $t ) { 00874 $this->mRedirectedFrom = $t; 00875 } 00876 00885 public function setPageTitle( $name ) { 00886 if ( $name instanceof Message ) { 00887 $name = $name->setContext( $this->getContext() )->text(); 00888 } 00889 00890 # change "<script>foo&bar</script>" to "<script>foo&bar</script>" 00891 # but leave "<i>foobar</i>" alone 00892 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) ); 00893 $this->mPagetitle = $nameWithTags; 00894 00895 # change "<i>foo&bar</i>" to "foo&bar" 00896 $this->setHTMLTitle( 00897 $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) 00898 ->inContentLanguage() 00899 ); 00900 } 00901 00907 public function getPageTitle() { 00908 return $this->mPagetitle; 00909 } 00910 00916 public function setTitle( Title $t ) { 00917 $this->getContext()->setTitle( $t ); 00918 } 00919 00925 public function setSubtitle( $str ) { 00926 $this->clearSubtitle(); 00927 $this->addSubtitle( $str ); 00928 } 00929 00936 public function appendSubtitle( $str ) { 00937 $this->addSubtitle( $str ); 00938 } 00939 00945 public function addSubtitle( $str ) { 00946 if ( $str instanceof Message ) { 00947 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse(); 00948 } else { 00949 $this->mSubtitle[] = $str; 00950 } 00951 } 00952 00958 public function addBacklinkSubtitle( Title $title ) { 00959 $query = array(); 00960 if ( $title->isRedirect() ) { 00961 $query['redirect'] = 'no'; 00962 } 00963 $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) ); 00964 } 00965 00969 public function clearSubtitle() { 00970 $this->mSubtitle = array(); 00971 } 00972 00978 public function getSubtitle() { 00979 return implode( "<br />\n\t\t\t\t", $this->mSubtitle ); 00980 } 00981 00986 public function setPrintable() { 00987 $this->mPrintable = true; 00988 } 00989 00995 public function isPrintable() { 00996 return $this->mPrintable; 00997 } 00998 01002 public function disable() { 01003 $this->mDoNothing = true; 01004 } 01005 01011 public function isDisabled() { 01012 return $this->mDoNothing; 01013 } 01014 01020 public function showNewSectionLink() { 01021 return $this->mNewSectionLink; 01022 } 01023 01029 public function forceHideNewSectionLink() { 01030 return $this->mHideNewSectionLink; 01031 } 01032 01041 public function setSyndicated( $show = true ) { 01042 if ( $show ) { 01043 $this->setFeedAppendQuery( false ); 01044 } else { 01045 $this->mFeedLinks = array(); 01046 } 01047 } 01048 01058 public function setFeedAppendQuery( $val ) { 01059 global $wgAdvertisedFeedTypes; 01060 01061 $this->mFeedLinks = array(); 01062 01063 foreach ( $wgAdvertisedFeedTypes as $type ) { 01064 $query = "feed=$type"; 01065 if ( is_string( $val ) ) { 01066 $query .= '&' . $val; 01067 } 01068 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query ); 01069 } 01070 } 01071 01078 public function addFeedLink( $format, $href ) { 01079 global $wgAdvertisedFeedTypes; 01080 01081 if ( in_array( $format, $wgAdvertisedFeedTypes ) ) { 01082 $this->mFeedLinks[$format] = $href; 01083 } 01084 } 01085 01090 public function isSyndicated() { 01091 return count( $this->mFeedLinks ) > 0; 01092 } 01093 01098 public function getSyndicationLinks() { 01099 return $this->mFeedLinks; 01100 } 01101 01107 public function getFeedAppendQuery() { 01108 return $this->mFeedLinksAppendQuery; 01109 } 01110 01118 public function setArticleFlag( $v ) { 01119 $this->mIsarticle = $v; 01120 if ( $v ) { 01121 $this->mIsArticleRelated = $v; 01122 } 01123 } 01124 01131 public function isArticle() { 01132 return $this->mIsarticle; 01133 } 01134 01141 public function setArticleRelated( $v ) { 01142 $this->mIsArticleRelated = $v; 01143 if ( !$v ) { 01144 $this->mIsarticle = false; 01145 } 01146 } 01147 01153 public function isArticleRelated() { 01154 return $this->mIsArticleRelated; 01155 } 01156 01163 public function addLanguageLinks( $newLinkArray ) { 01164 $this->mLanguageLinks += $newLinkArray; 01165 } 01166 01173 public function setLanguageLinks( $newLinkArray ) { 01174 $this->mLanguageLinks = $newLinkArray; 01175 } 01176 01182 public function getLanguageLinks() { 01183 return $this->mLanguageLinks; 01184 } 01185 01191 public function addCategoryLinks( $categories ) { 01192 global $wgContLang; 01193 01194 if ( !is_array( $categories ) || count( $categories ) == 0 ) { 01195 return; 01196 } 01197 01198 # Add the links to a LinkBatch 01199 $arr = array( NS_CATEGORY => $categories ); 01200 $lb = new LinkBatch; 01201 $lb->setArray( $arr ); 01202 01203 # Fetch existence plus the hiddencat property 01204 $dbr = wfGetDB( DB_SLAVE ); 01205 $res = $dbr->select( array( 'page', 'page_props' ), 01206 array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ), 01207 $lb->constructSet( 'page', $dbr ), 01208 __METHOD__, 01209 array(), 01210 array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) ) 01211 ); 01212 01213 # Add the results to the link cache 01214 $lb->addResultToCache( LinkCache::singleton(), $res ); 01215 01216 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+ 01217 $categories = array_combine( 01218 array_keys( $categories ), 01219 array_fill( 0, count( $categories ), 'normal' ) 01220 ); 01221 01222 # Mark hidden categories 01223 foreach ( $res as $row ) { 01224 if ( isset( $row->pp_value ) ) { 01225 $categories[$row->page_title] = 'hidden'; 01226 } 01227 } 01228 01229 # Add the remaining categories to the skin 01230 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) { 01231 foreach ( $categories as $category => $type ) { 01232 $origcategory = $category; 01233 $title = Title::makeTitleSafe( NS_CATEGORY, $category ); 01234 $wgContLang->findVariantLink( $category, $title, true ); 01235 if ( $category != $origcategory ) { 01236 if ( array_key_exists( $category, $categories ) ) { 01237 continue; 01238 } 01239 } 01240 $text = $wgContLang->convertHtml( $title->getText() ); 01241 $this->mCategories[] = $title->getText(); 01242 $this->mCategoryLinks[$type][] = Linker::link( $title, $text ); 01243 } 01244 } 01245 } 01246 01252 public function setCategoryLinks( $categories ) { 01253 $this->mCategoryLinks = array(); 01254 $this->addCategoryLinks( $categories ); 01255 } 01256 01265 public function getCategoryLinks() { 01266 return $this->mCategoryLinks; 01267 } 01268 01274 public function getCategories() { 01275 return $this->mCategories; 01276 } 01277 01284 public function disallowUserJs() { 01285 $this->reduceAllowedModuleOrigin( ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL ); 01286 } 01287 01294 public function isUserJsAllowed() { 01295 wfDeprecated( __METHOD__, '1.18' ); 01296 return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL; 01297 } 01298 01307 public function getAllowedModules( $type = null ) { 01308 return $this->mAllowedModuleOrigin; 01309 } 01310 01320 public function setAllowedModules( $type, $level ) { 01321 wfDeprecated( __METHOD__, '1.24' ); 01322 $this->reduceAllowedModuleOrigin( $level ); 01323 } 01324 01334 public function reduceAllowedModules( $type, $level ) { 01335 wfDeprecated( __METHOD__, '1.24' ); 01336 $this->reduceAllowedModuleOrigin( $level ); 01337 } 01338 01347 public function reduceAllowedModuleOrigin( $level ) { 01348 $this->mAllowedModuleOrigin = min( $this->mAllowedModuleOrigin, $level ); 01349 } 01350 01356 public function prependHTML( $text ) { 01357 $this->mBodytext = $text . $this->mBodytext; 01358 } 01359 01365 public function addHTML( $text ) { 01366 $this->mBodytext .= $text; 01367 } 01368 01378 public function addElement( $element, $attribs = array(), $contents = '' ) { 01379 $this->addHTML( Html::element( $element, $attribs, $contents ) ); 01380 } 01381 01385 public function clearHTML() { 01386 $this->mBodytext = ''; 01387 } 01388 01394 public function getHTML() { 01395 return $this->mBodytext; 01396 } 01397 01405 public function parserOptions( $options = null ) { 01406 if ( !$this->mParserOptions ) { 01407 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() ); 01408 $this->mParserOptions->setEditSection( false ); 01409 } 01410 return wfSetVar( $this->mParserOptions, $options ); 01411 } 01412 01420 public function setRevisionId( $revid ) { 01421 $val = is_null( $revid ) ? null : intval( $revid ); 01422 return wfSetVar( $this->mRevisionId, $val ); 01423 } 01424 01430 public function getRevisionId() { 01431 return $this->mRevisionId; 01432 } 01433 01441 public function setRevisionTimestamp( $timestamp ) { 01442 return wfSetVar( $this->mRevisionTimestamp, $timestamp ); 01443 } 01444 01451 public function getRevisionTimestamp() { 01452 return $this->mRevisionTimestamp; 01453 } 01454 01461 public function setFileVersion( $file ) { 01462 $val = null; 01463 if ( $file instanceof File && $file->exists() ) { 01464 $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ); 01465 } 01466 return wfSetVar( $this->mFileVersion, $val, true ); 01467 } 01468 01474 public function getFileVersion() { 01475 return $this->mFileVersion; 01476 } 01477 01484 public function getTemplateIds() { 01485 return $this->mTemplateIds; 01486 } 01487 01494 public function getFileSearchOptions() { 01495 return $this->mImageTimeKeys; 01496 } 01497 01506 public function addWikiText( $text, $linestart = true, $interface = true ) { 01507 $title = $this->getTitle(); // Work around E_STRICT 01508 if ( !$title ) { 01509 throw new MWException( 'Title is null' ); 01510 } 01511 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface ); 01512 } 01513 01521 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) { 01522 $this->addWikiTextTitle( $text, $title, $linestart ); 01523 } 01524 01532 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) { 01533 $this->addWikiTextTitle( $text, $title, $linestart, true ); 01534 } 01535 01542 public function addWikiTextTidy( $text, $linestart = true ) { 01543 $title = $this->getTitle(); 01544 $this->addWikiTextTitleTidy( $text, $title, $linestart ); 01545 } 01546 01557 public function addWikiTextTitle( $text, Title $title, $linestart, $tidy = false, $interface = false ) { 01558 global $wgParser; 01559 01560 wfProfileIn( __METHOD__ ); 01561 01562 $popts = $this->parserOptions(); 01563 $oldTidy = $popts->setTidy( $tidy ); 01564 $popts->setInterfaceMessage( (bool)$interface ); 01565 01566 $parserOutput = $wgParser->parse( 01567 $text, $title, $popts, 01568 $linestart, true, $this->mRevisionId 01569 ); 01570 01571 $popts->setTidy( $oldTidy ); 01572 01573 $this->addParserOutput( $parserOutput ); 01574 01575 wfProfileOut( __METHOD__ ); 01576 } 01577 01583 public function addParserOutputNoText( &$parserOutput ) { 01584 $this->mLanguageLinks += $parserOutput->getLanguageLinks(); 01585 $this->addCategoryLinks( $parserOutput->getCategories() ); 01586 $this->mNewSectionLink = $parserOutput->getNewSection(); 01587 $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); 01588 01589 $this->mParseWarnings = $parserOutput->getWarnings(); 01590 if ( !$parserOutput->isCacheable() ) { 01591 $this->enableClientCache( false ); 01592 } 01593 $this->mNoGallery = $parserOutput->getNoGallery(); 01594 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() ); 01595 $this->addModules( $parserOutput->getModules() ); 01596 $this->addModuleScripts( $parserOutput->getModuleScripts() ); 01597 $this->addModuleStyles( $parserOutput->getModuleStyles() ); 01598 $this->addModuleMessages( $parserOutput->getModuleMessages() ); 01599 $this->addJsConfigVars( $parserOutput->getJsConfigVars() ); 01600 $this->mPreventClickjacking = $this->mPreventClickjacking 01601 || $parserOutput->preventClickjacking(); 01602 01603 // Template versioning... 01604 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) { 01605 if ( isset( $this->mTemplateIds[$ns] ) ) { 01606 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns]; 01607 } else { 01608 $this->mTemplateIds[$ns] = $dbks; 01609 } 01610 } 01611 // File versioning... 01612 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) { 01613 $this->mImageTimeKeys[$dbk] = $data; 01614 } 01615 01616 // Hooks registered in the object 01617 global $wgParserOutputHooks; 01618 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) { 01619 list( $hookName, $data ) = $hookInfo; 01620 if ( isset( $wgParserOutputHooks[$hookName] ) ) { 01621 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data ); 01622 } 01623 } 01624 01625 // Link flags are ignored for now, but may in the future be 01626 // used to mark individual language links. 01627 $linkFlags = array(); 01628 wfRunHooks( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) ); 01629 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); 01630 } 01631 01637 function addParserOutput( &$parserOutput ) { 01638 $this->addParserOutputNoText( $parserOutput ); 01639 $parserOutput->setTOCEnabled( $this->mEnableTOC ); 01640 01641 // Touch section edit links only if not previously disabled 01642 if ( $parserOutput->getEditSectionTokens() ) { 01643 $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks ); 01644 } 01645 $text = $parserOutput->getText(); 01646 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) ); 01647 $this->addHTML( $text ); 01648 } 01649 01655 public function addTemplate( &$template ) { 01656 $this->addHTML( $template->getHTML() ); 01657 } 01658 01671 public function parse( $text, $linestart = true, $interface = false, $language = null ) { 01672 global $wgParser; 01673 01674 if ( is_null( $this->getTitle() ) ) { 01675 throw new MWException( 'Empty $mTitle in ' . __METHOD__ ); 01676 } 01677 01678 $popts = $this->parserOptions(); 01679 if ( $interface ) { 01680 $popts->setInterfaceMessage( true ); 01681 } 01682 if ( $language !== null ) { 01683 $oldLang = $popts->setTargetLanguage( $language ); 01684 } 01685 01686 $parserOutput = $wgParser->parse( 01687 $text, $this->getTitle(), $popts, 01688 $linestart, true, $this->mRevisionId 01689 ); 01690 01691 if ( $interface ) { 01692 $popts->setInterfaceMessage( false ); 01693 } 01694 if ( $language !== null ) { 01695 $popts->setTargetLanguage( $oldLang ); 01696 } 01697 01698 return $parserOutput->getText(); 01699 } 01700 01711 public function parseInline( $text, $linestart = true, $interface = false ) { 01712 $parsed = $this->parse( $text, $linestart, $interface ); 01713 01714 $m = array(); 01715 if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) { 01716 $parsed = $m[1]; 01717 } 01718 01719 return $parsed; 01720 } 01721 01727 public function setSquidMaxage( $maxage ) { 01728 $this->mSquidMaxage = $maxage; 01729 } 01730 01738 public function enableClientCache( $state ) { 01739 return wfSetVar( $this->mEnableClientCache, $state ); 01740 } 01741 01747 function getCacheVaryCookies() { 01748 global $wgCookiePrefix, $wgCacheVaryCookies; 01749 static $cookies; 01750 if ( $cookies === null ) { 01751 $cookies = array_merge( 01752 array( 01753 "{$wgCookiePrefix}Token", 01754 "{$wgCookiePrefix}LoggedOut", 01755 "forceHTTPS", 01756 session_name() 01757 ), 01758 $wgCacheVaryCookies 01759 ); 01760 wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) ); 01761 } 01762 return $cookies; 01763 } 01764 01771 function haveCacheVaryCookies() { 01772 $cookieHeader = $this->getRequest()->getHeader( 'cookie' ); 01773 if ( $cookieHeader === false ) { 01774 return false; 01775 } 01776 $cvCookies = $this->getCacheVaryCookies(); 01777 foreach ( $cvCookies as $cookieName ) { 01778 # Check for a simple string match, like the way squid does it 01779 if ( strpos( $cookieHeader, $cookieName ) !== false ) { 01780 wfDebug( __METHOD__ . ": found $cookieName\n" ); 01781 return true; 01782 } 01783 } 01784 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" ); 01785 return false; 01786 } 01787 01796 public function addVaryHeader( $header, $option = null ) { 01797 if ( !array_key_exists( $header, $this->mVaryHeader ) ) { 01798 $this->mVaryHeader[$header] = (array)$option; 01799 } elseif ( is_array( $option ) ) { 01800 if ( is_array( $this->mVaryHeader[$header] ) ) { 01801 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option ); 01802 } else { 01803 $this->mVaryHeader[$header] = $option; 01804 } 01805 } 01806 $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] ); 01807 } 01808 01815 public function getVaryHeader() { 01816 return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ); 01817 } 01818 01824 public function getXVO() { 01825 $cvCookies = $this->getCacheVaryCookies(); 01826 01827 $cookiesOption = array(); 01828 foreach ( $cvCookies as $cookieName ) { 01829 $cookiesOption[] = 'string-contains=' . $cookieName; 01830 } 01831 $this->addVaryHeader( 'Cookie', $cookiesOption ); 01832 01833 $headers = array(); 01834 foreach ( $this->mVaryHeader as $header => $option ) { 01835 $newheader = $header; 01836 if ( is_array( $option ) && count( $option ) > 0 ) { 01837 $newheader .= ';' . implode( ';', $option ); 01838 } 01839 $headers[] = $newheader; 01840 } 01841 $xvo = 'X-Vary-Options: ' . implode( ',', $headers ); 01842 01843 return $xvo; 01844 } 01845 01854 function addAcceptLanguage() { 01855 $lang = $this->getTitle()->getPageLanguage(); 01856 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) { 01857 $variants = $lang->getVariants(); 01858 $aloption = array(); 01859 foreach ( $variants as $variant ) { 01860 if ( $variant === $lang->getCode() ) { 01861 continue; 01862 } else { 01863 $aloption[] = 'string-contains=' . $variant; 01864 01865 // IE and some other browsers use BCP 47 standards in 01866 // their Accept-Language header, like "zh-CN" or "zh-Hant". 01867 // We should handle these too. 01868 $variantBCP47 = wfBCP47( $variant ); 01869 if ( $variantBCP47 !== $variant ) { 01870 $aloption[] = 'string-contains=' . $variantBCP47; 01871 } 01872 } 01873 } 01874 $this->addVaryHeader( 'Accept-Language', $aloption ); 01875 } 01876 } 01877 01888 public function preventClickjacking( $enable = true ) { 01889 $this->mPreventClickjacking = $enable; 01890 } 01891 01897 public function allowClickjacking() { 01898 $this->mPreventClickjacking = false; 01899 } 01900 01907 public function getPreventClickjacking() { 01908 return $this->mPreventClickjacking; 01909 } 01910 01918 public function getFrameOptions() { 01919 global $wgBreakFrames, $wgEditPageFrameOptions; 01920 if ( $wgBreakFrames ) { 01921 return 'DENY'; 01922 } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) { 01923 return $wgEditPageFrameOptions; 01924 } 01925 return false; 01926 } 01927 01931 public function sendCacheControl() { 01932 global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO; 01933 01934 $response = $this->getRequest()->response(); 01935 if ( $wgUseETag && $this->mETag ) { 01936 $response->header( "ETag: $this->mETag" ); 01937 } 01938 01939 $this->addVaryHeader( 'Cookie' ); 01940 $this->addAcceptLanguage(); 01941 01942 # don't serve compressed data to clients who can't handle it 01943 # maintain different caches for logged-in users and non-logged in ones 01944 $response->header( $this->getVaryHeader() ); 01945 01946 if ( $wgUseXVO ) { 01947 # Add an X-Vary-Options header for Squid with Wikimedia patches 01948 $response->header( $this->getXVO() ); 01949 } 01950 01951 if ( $this->mEnableClientCache ) { 01952 if ( 01953 $wgUseSquid && session_id() == '' && !$this->isPrintable() && 01954 $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() 01955 ) { 01956 if ( $wgUseESI ) { 01957 # We'll purge the proxy cache explicitly, but require end user agents 01958 # to revalidate against the proxy on each visit. 01959 # Surrogate-Control controls our Squid, Cache-Control downstream caches 01960 wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", 'log' ); 01961 # start with a shorter timeout for initial testing 01962 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"'); 01963 $response->header( 'Surrogate-Control: max-age=' . $wgSquidMaxage . '+' . $this->mSquidMaxage . ', content="ESI/1.0"' ); 01964 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); 01965 } else { 01966 # We'll purge the proxy cache for anons explicitly, but require end user agents 01967 # to revalidate against the proxy on each visit. 01968 # IMPORTANT! The Squid needs to replace the Cache-Control header with 01969 # Cache-Control: s-maxage=0, must-revalidate, max-age=0 01970 wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", 'log' ); 01971 # start with a shorter timeout for initial testing 01972 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" ); 01973 $response->header( 'Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0' ); 01974 } 01975 } else { 01976 # We do want clients to cache if they can, but they *must* check for updates 01977 # on revisiting the page. 01978 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", 'log' ); 01979 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01980 $response->header( "Cache-Control: private, must-revalidate, max-age=0" ); 01981 } 01982 if ( $this->mLastModified ) { 01983 $response->header( "Last-Modified: {$this->mLastModified}" ); 01984 } 01985 } else { 01986 wfDebug( __METHOD__ . ": no caching **\n", 'log' ); 01987 01988 # In general, the absence of a last modified header should be enough to prevent 01989 # the client from using its cache. We send a few other things just to make sure. 01990 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01991 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); 01992 $response->header( 'Pragma: no-cache' ); 01993 } 01994 } 01995 02004 public static function getStatusMessage( $code ) { 02005 wfDeprecated( __METHOD__, '1.18' ); 02006 return HttpStatus::getMessage( $code ); 02007 } 02008 02013 public function output() { 02014 global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP, 02015 $wgUseAjax, $wgResponsiveImages; 02016 02017 if ( $this->mDoNothing ) { 02018 return; 02019 } 02020 02021 wfProfileIn( __METHOD__ ); 02022 02023 $response = $this->getRequest()->response(); 02024 02025 if ( $this->mRedirect != '' ) { 02026 # Standards require redirect URLs to be absolute 02027 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT ); 02028 02029 $redirect = $this->mRedirect; 02030 $code = $this->mRedirectCode; 02031 02032 if ( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) { 02033 if ( $code == '301' || $code == '303' ) { 02034 if ( !$wgDebugRedirects ) { 02035 $message = HttpStatus::getMessage( $code ); 02036 $response->header( "HTTP/1.1 $code $message" ); 02037 } 02038 $this->mLastModified = wfTimestamp( TS_RFC2822 ); 02039 } 02040 if ( $wgVaryOnXFP ) { 02041 $this->addVaryHeader( 'X-Forwarded-Proto' ); 02042 } 02043 $this->sendCacheControl(); 02044 02045 $response->header( "Content-Type: text/html; charset=utf-8" ); 02046 if ( $wgDebugRedirects ) { 02047 $url = htmlspecialchars( $redirect ); 02048 print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n"; 02049 print "<p>Location: <a href=\"$url\">$url</a></p>\n"; 02050 print "</body>\n</html>\n"; 02051 } else { 02052 $response->header( 'Location: ' . $redirect ); 02053 } 02054 } 02055 02056 wfProfileOut( __METHOD__ ); 02057 return; 02058 } elseif ( $this->mStatusCode ) { 02059 $message = HttpStatus::getMessage( $this->mStatusCode ); 02060 if ( $message ) { 02061 $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message ); 02062 } 02063 } 02064 02065 # Buffer output; final headers may depend on later processing 02066 ob_start(); 02067 02068 $response->header( "Content-type: $wgMimeType; charset=UTF-8" ); 02069 $response->header( 'Content-language: ' . $wgLanguageCode ); 02070 02071 // Prevent framing, if requested 02072 $frameOptions = $this->getFrameOptions(); 02073 if ( $frameOptions ) { 02074 $response->header( "X-Frame-Options: $frameOptions" ); 02075 } 02076 02077 if ( $this->mArticleBodyOnly ) { 02078 echo $this->mBodytext; 02079 } else { 02080 02081 $sk = $this->getSkin(); 02082 // add skin specific modules 02083 $modules = $sk->getDefaultModules(); 02084 02085 // enforce various default modules for all skins 02086 $coreModules = array( 02087 // keep this list as small as possible 02088 'mediawiki.page.startup', 02089 'mediawiki.user', 02090 ); 02091 02092 // Support for high-density display images if enabled 02093 if ( $wgResponsiveImages ) { 02094 $coreModules[] = 'mediawiki.hidpi'; 02095 } 02096 02097 $this->addModules( $coreModules ); 02098 foreach ( $modules as $group ) { 02099 $this->addModules( $group ); 02100 } 02101 MWDebug::addModules( $this ); 02102 if ( $wgUseAjax ) { 02103 // FIXME: deprecate? - not clear why this is useful 02104 wfRunHooks( 'AjaxAddScript', array( &$this ) ); 02105 } 02106 02107 // Hook that allows last minute changes to the output page, e.g. 02108 // adding of CSS or Javascript by extensions. 02109 wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) ); 02110 02111 wfProfileIn( 'Output-skin' ); 02112 $sk->outputPage(); 02113 wfProfileOut( 'Output-skin' ); 02114 } 02115 02116 // This hook allows last minute changes to final overall output by modifying output buffer 02117 wfRunHooks( 'AfterFinalPageOutput', array( $this ) ); 02118 02119 $this->sendCacheControl(); 02120 02121 ob_end_flush(); 02122 02123 wfProfileOut( __METHOD__ ); 02124 } 02125 02132 public function out( $ins ) { 02133 wfDeprecated( __METHOD__, '1.22' ); 02134 print $ins; 02135 } 02136 02141 function blockedPage() { 02142 throw new UserBlockedError( $this->getUser()->mBlock ); 02143 } 02144 02155 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) { 02156 $this->setPageTitle( $pageTitle ); 02157 if ( $htmlTitle !== false ) { 02158 $this->setHTMLTitle( $htmlTitle ); 02159 } 02160 $this->setRobotPolicy( 'noindex,nofollow' ); 02161 $this->setArticleRelated( false ); 02162 $this->enableClientCache( false ); 02163 $this->mRedirect = ''; 02164 $this->clearSubtitle(); 02165 $this->clearHTML(); 02166 } 02167 02180 public function showErrorPage( $title, $msg, $params = array() ) { 02181 if ( !$title instanceof Message ) { 02182 $title = $this->msg( $title ); 02183 } 02184 02185 $this->prepareErrorPage( $title ); 02186 02187 if ( $msg instanceof Message ) { 02188 if ( $params !== array() ) { 02189 trigger_error( 'Argument ignored: $params. The message parameters argument is discarded when the $msg argument is a Message object instead of a string.', E_USER_NOTICE ); 02190 } 02191 $this->addHTML( $msg->parseAsBlock() ); 02192 } else { 02193 $this->addWikiMsgArray( $msg, $params ); 02194 } 02195 02196 $this->returnToMain(); 02197 } 02198 02205 public function showPermissionsErrorPage( $errors, $action = null ) { 02206 // For some action (read, edit, create and upload), display a "login to do this action" 02207 // error if all of the following conditions are met: 02208 // 1. the user is not logged in 02209 // 2. the only error is insufficient permissions (i.e. no block or something else) 02210 // 3. the error can be avoided simply by logging in 02211 if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) ) 02212 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] ) 02213 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' ) 02214 && ( User::groupHasPermission( 'user', $action ) 02215 || User::groupHasPermission( 'autoconfirmed', $action ) ) 02216 ) { 02217 $displayReturnto = null; 02218 02219 # Due to bug 32276, if a user does not have read permissions, 02220 # $this->getTitle() will just give Special:Badtitle, which is 02221 # not especially useful as a returnto parameter. Use the title 02222 # from the request instead, if there was one. 02223 $request = $this->getRequest(); 02224 $returnto = Title::newFromURL( $request->getVal( 'title', '' ) ); 02225 if ( $action == 'edit' ) { 02226 $msg = 'whitelistedittext'; 02227 $displayReturnto = $returnto; 02228 } elseif ( $action == 'createpage' || $action == 'createtalk' ) { 02229 $msg = 'nocreatetext'; 02230 } elseif ( $action == 'upload' ) { 02231 $msg = 'uploadnologintext'; 02232 } else { # Read 02233 $msg = 'loginreqpagetext'; 02234 $displayReturnto = Title::newMainPage(); 02235 } 02236 02237 $query = array(); 02238 02239 if ( $returnto ) { 02240 $query['returnto'] = $returnto->getPrefixedText(); 02241 02242 if ( !$request->wasPosted() ) { 02243 $returntoquery = $request->getValues(); 02244 unset( $returntoquery['title'] ); 02245 unset( $returntoquery['returnto'] ); 02246 unset( $returntoquery['returntoquery'] ); 02247 $query['returntoquery'] = wfArrayToCgi( $returntoquery ); 02248 } 02249 } 02250 $loginLink = Linker::linkKnown( 02251 SpecialPage::getTitleFor( 'Userlogin' ), 02252 $this->msg( 'loginreqlink' )->escaped(), 02253 array(), 02254 $query 02255 ); 02256 02257 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) ); 02258 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() ); 02259 02260 # Don't return to a page the user can't read otherwise 02261 # we'll end up in a pointless loop 02262 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) { 02263 $this->returnToMain( null, $displayReturnto ); 02264 } 02265 } else { 02266 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) ); 02267 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) ); 02268 } 02269 } 02270 02277 public function versionRequired( $version ) { 02278 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) ); 02279 02280 $this->addWikiMsg( 'versionrequiredtext', $version ); 02281 $this->returnToMain(); 02282 } 02283 02290 public function permissionRequired( $permission ) { 02291 throw new PermissionsError( $permission ); 02292 } 02293 02299 public function loginToUse() { 02300 throw new PermissionsError( 'read' ); 02301 } 02302 02310 public function formatPermissionsErrorMessage( $errors, $action = null ) { 02311 if ( $action == null ) { 02312 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n"; 02313 } else { 02314 $action_desc = $this->msg( "action-$action" )->plain(); 02315 $text = $this->msg( 02316 'permissionserrorstext-withaction', 02317 count( $errors ), 02318 $action_desc 02319 )->plain() . "\n\n"; 02320 } 02321 02322 if ( count( $errors ) > 1 ) { 02323 $text .= '<ul class="permissions-errors">' . "\n"; 02324 02325 foreach ( $errors as $error ) { 02326 $text .= '<li>'; 02327 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain(); 02328 $text .= "</li>\n"; 02329 } 02330 $text .= '</ul>'; 02331 } else { 02332 $text .= "<div class=\"permissions-errors\">\n" . 02333 call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() . 02334 "\n</div>"; 02335 } 02336 02337 return $text; 02338 } 02339 02361 public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) { 02362 $this->setRobotPolicy( 'noindex,nofollow' ); 02363 $this->setArticleRelated( false ); 02364 02365 // If no reason is given, just supply a default "I can't let you do 02366 // that, Dave" message. Should only occur if called by legacy code. 02367 if ( $protected && empty( $reasons ) ) { 02368 $reasons[] = array( 'badaccess-group0' ); 02369 } 02370 02371 if ( !empty( $reasons ) ) { 02372 // Permissions error 02373 if ( $source ) { 02374 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) ); 02375 $this->addBacklinkSubtitle( $this->getTitle() ); 02376 } else { 02377 $this->setPageTitle( $this->msg( 'badaccess' ) ); 02378 } 02379 $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) ); 02380 } else { 02381 // Wiki is read only 02382 throw new ReadOnlyError; 02383 } 02384 02385 // Show source, if supplied 02386 if ( is_string( $source ) ) { 02387 $this->addWikiMsg( 'viewsourcetext' ); 02388 02389 $pageLang = $this->getTitle()->getPageLanguage(); 02390 $params = array( 02391 'id' => 'wpTextbox1', 02392 'name' => 'wpTextbox1', 02393 'cols' => $this->getUser()->getOption( 'cols' ), 02394 'rows' => $this->getUser()->getOption( 'rows' ), 02395 'readonly' => 'readonly', 02396 'lang' => $pageLang->getHtmlCode(), 02397 'dir' => $pageLang->getDir(), 02398 ); 02399 $this->addHTML( Html::element( 'textarea', $params, $source ) ); 02400 02401 // Show templates used by this article 02402 $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() ); 02403 $this->addHTML( "<div class='templatesUsed'> 02404 $templates 02405 </div> 02406 " ); 02407 } 02408 02409 # If the title doesn't exist, it's fairly pointless to print a return 02410 # link to it. After all, you just tried editing it and couldn't, so 02411 # what's there to do there? 02412 if ( $this->getTitle()->exists() ) { 02413 $this->returnToMain( null, $this->getTitle() ); 02414 } 02415 } 02416 02421 public function rateLimited() { 02422 throw new ThrottledError; 02423 } 02424 02434 public function showLagWarning( $lag ) { 02435 global $wgSlaveLagWarning, $wgSlaveLagCritical; 02436 if ( $lag >= $wgSlaveLagWarning ) { 02437 $message = $lag < $wgSlaveLagCritical 02438 ? 'lag-warn-normal' 02439 : 'lag-warn-high'; 02440 $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" ); 02441 $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) ); 02442 } 02443 } 02444 02445 public function showFatalError( $message ) { 02446 $this->prepareErrorPage( $this->msg( 'internalerror' ) ); 02447 02448 $this->addHTML( $message ); 02449 } 02450 02451 public function showUnexpectedValueError( $name, $val ) { 02452 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() ); 02453 } 02454 02455 public function showFileCopyError( $old, $new ) { 02456 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() ); 02457 } 02458 02459 public function showFileRenameError( $old, $new ) { 02460 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() ); 02461 } 02462 02463 public function showFileDeleteError( $name ) { 02464 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() ); 02465 } 02466 02467 public function showFileNotFoundError( $name ) { 02468 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() ); 02469 } 02470 02479 public function addReturnTo( $title, $query = array(), $text = null, $options = array() ) { 02480 $link = $this->msg( 'returnto' )->rawParams( 02481 Linker::link( $title, $text, array(), $query, $options ) )->escaped(); 02482 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" ); 02483 } 02484 02493 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) { 02494 if ( $returnto == null ) { 02495 $returnto = $this->getRequest()->getText( 'returnto' ); 02496 } 02497 02498 if ( $returntoquery == null ) { 02499 $returntoquery = $this->getRequest()->getText( 'returntoquery' ); 02500 } 02501 02502 if ( $returnto === '' ) { 02503 $returnto = Title::newMainPage(); 02504 } 02505 02506 if ( is_object( $returnto ) ) { 02507 $titleObj = $returnto; 02508 } else { 02509 $titleObj = Title::newFromText( $returnto ); 02510 } 02511 if ( !is_object( $titleObj ) ) { 02512 $titleObj = Title::newMainPage(); 02513 } 02514 02515 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) ); 02516 } 02517 02523 public function headElement( Skin $sk, $includeStyle = true ) { 02524 global $wgContLang, $wgMimeType; 02525 02526 $userdir = $this->getLanguage()->getDir(); 02527 $sitedir = $wgContLang->getDir(); 02528 02529 $ret = Html::htmlHeader( $sk->getHtmlElementAttributes() ); 02530 02531 if ( $this->getHTMLTitle() == '' ) { 02532 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() ); 02533 } 02534 02535 $openHead = Html::openElement( 'head' ); 02536 if ( $openHead ) { 02537 # Don't bother with the newline if $head == '' 02538 $ret .= "$openHead\n"; 02539 } 02540 02541 if ( !Html::isXmlMimeType( $wgMimeType ) ) { 02542 // Add <meta charset="UTF-8"> 02543 // This should be before <title> since it defines the charset used by 02544 // text including the text inside <title>. 02545 // The spec recommends defining XHTML5's charset using the XML declaration 02546 // instead of meta. 02547 // Our XML declaration is output by Html::htmlHeader. 02548 // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type 02549 // http://www.whatwg.org/html/semantics.html#charset 02550 $ret .= Html::element( 'meta', array( 'charset' => 'UTF-8' ) ) . "\n"; 02551 } 02552 02553 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n"; 02554 02555 // Avoid Internet Explorer "compatibility view", so that 02556 // jQuery can work correctly. 02557 $ret .= Html::element( 'meta', array( 'http-equiv' => 'X-UA-Compatible', 'content' => 'IE=EDGE' ) ) . "\n"; 02558 02559 $ret .= ( 02560 $this->getHeadLinks() . 02561 "\n" . 02562 $this->buildCssLinks() . 02563 // No newline after buildCssLinks since makeResourceLoaderLink did that already 02564 $this->getHeadScripts() . 02565 "\n" . 02566 $this->getHeadItems() 02567 ); 02568 02569 $closeHead = Html::closeElement( 'head' ); 02570 if ( $closeHead ) { 02571 $ret .= "$closeHead\n"; 02572 } 02573 02574 $bodyClasses = array(); 02575 $bodyClasses[] = 'mediawiki'; 02576 02577 # Classes for LTR/RTL directionality support 02578 $bodyClasses[] = $userdir; 02579 $bodyClasses[] = "sitedir-$sitedir"; 02580 02581 if ( $this->getLanguage()->capitalizeAllNouns() ) { 02582 # A <body> class is probably not the best way to do this . . . 02583 $bodyClasses[] = 'capitalize-all-nouns'; 02584 } 02585 02586 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() ); 02587 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() ); 02588 $bodyClasses[] = 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) ); 02589 02590 $bodyAttrs = array(); 02591 // While the implode() is not strictly needed, it's used for backwards compatibility 02592 // (this used to be built as a string and hooks likely still expect that). 02593 $bodyAttrs['class'] = implode( ' ', $bodyClasses ); 02594 02595 // Allow skins and extensions to add body attributes they need 02596 $sk->addToBodyAttributes( $this, $bodyAttrs ); 02597 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) ); 02598 02599 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n"; 02600 02601 return $ret; 02602 } 02603 02609 public function getResourceLoader() { 02610 if ( is_null( $this->mResourceLoader ) ) { 02611 $this->mResourceLoader = new ResourceLoader(); 02612 } 02613 return $this->mResourceLoader; 02614 } 02615 02625 protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) { 02626 global $wgResourceLoaderUseESI; 02627 02628 $modules = (array)$modules; 02629 02630 $links = array( 02631 'html' => '', 02632 'states' => array(), 02633 ); 02634 02635 if ( !count( $modules ) ) { 02636 return $links; 02637 } 02638 02639 02640 if ( count( $modules ) > 1 ) { 02641 // Remove duplicate module requests 02642 $modules = array_unique( $modules ); 02643 // Sort module names so requests are more uniform 02644 sort( $modules ); 02645 02646 if ( ResourceLoader::inDebugMode() ) { 02647 // Recursively call us for every item 02648 foreach ( $modules as $name ) { 02649 $link = $this->makeResourceLoaderLink( $name, $only, $useESI ); 02650 $links['html'] .= $link['html']; 02651 $links['states'] += $link['states']; 02652 } 02653 return $links; 02654 } 02655 } 02656 02657 if ( !is_null( $this->mTarget ) ) { 02658 $extraQuery['target'] = $this->mTarget; 02659 } 02660 02661 // Create keyed-by-group list of module objects from modules list 02662 $groups = array(); 02663 $resourceLoader = $this->getResourceLoader(); 02664 foreach ( $modules as $name ) { 02665 $module = $resourceLoader->getModule( $name ); 02666 # Check that we're allowed to include this module on this page 02667 if ( !$module 02668 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) 02669 && $only == ResourceLoaderModule::TYPE_SCRIPTS ) 02670 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES ) 02671 && $only == ResourceLoaderModule::TYPE_STYLES ) 02672 || ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) 02673 ) { 02674 continue; 02675 } 02676 02677 $group = $module->getGroup(); 02678 if ( !isset( $groups[$group] ) ) { 02679 $groups[$group] = array(); 02680 } 02681 $groups[$group][$name] = $module; 02682 } 02683 02684 foreach ( $groups as $group => $grpModules ) { 02685 // Special handling for user-specific groups 02686 $user = null; 02687 if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) { 02688 $user = $this->getUser()->getName(); 02689 } 02690 02691 // Create a fake request based on the one we are about to make so modules return 02692 // correct timestamp and emptiness data 02693 $query = ResourceLoader::makeLoaderQuery( 02694 array(), // modules; not determined yet 02695 $this->getLanguage()->getCode(), 02696 $this->getSkin()->getSkinName(), 02697 $user, 02698 null, // version; not determined yet 02699 ResourceLoader::inDebugMode(), 02700 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02701 $this->isPrintable(), 02702 $this->getRequest()->getBool( 'handheld' ), 02703 $extraQuery 02704 ); 02705 $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); 02706 02707 // Extract modules that know they're empty 02708 foreach ( $grpModules as $key => $module ) { 02709 // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857) 02710 // If we're only getting the styles, we don't need to do anything for empty modules. 02711 if ( $module->isKnownEmpty( $context ) ) { 02712 unset( $grpModules[$key] ); 02713 if ( $only !== ResourceLoaderModule::TYPE_STYLES ) { 02714 $links['states'][$key] = 'ready'; 02715 } 02716 } 02717 } 02718 02719 // If there are no non-empty modules, skip this group 02720 if ( count( $grpModules ) === 0 ) { 02721 continue; 02722 } 02723 02724 // Inline private modules. These can't be loaded through load.php for security 02725 // reasons, see bug 34907. Note that these modules should be loaded from 02726 // getHeadScripts() before the first loader call. Otherwise other modules can't 02727 // properly use them as dependencies (bug 30914) 02728 if ( $group === 'private' ) { 02729 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02730 $links['html'] .= Html::inlineStyle( 02731 $resourceLoader->makeModuleResponse( $context, $grpModules ) 02732 ); 02733 } else { 02734 $links['html'] .= Html::inlineScript( 02735 ResourceLoader::makeLoaderConditionalScript( 02736 $resourceLoader->makeModuleResponse( $context, $grpModules ) 02737 ) 02738 ); 02739 } 02740 $links['html'] .= "\n"; 02741 continue; 02742 } 02743 02744 // Special handling for the user group; because users might change their stuff 02745 // on-wiki like user pages, or user preferences; we need to find the highest 02746 // timestamp of these user-changeable modules so we can ensure cache misses on change 02747 // This should NOT be done for the site group (bug 27564) because anons get that too 02748 // and we shouldn't be putting timestamps in Squid-cached HTML 02749 $version = null; 02750 if ( $group === 'user' ) { 02751 // Get the maximum timestamp 02752 $timestamp = 1; 02753 foreach ( $grpModules as $module ) { 02754 $timestamp = max( $timestamp, $module->getModifiedTime( $context ) ); 02755 } 02756 // Add a version parameter so cache will break when things change 02757 $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp ); 02758 } 02759 02760 $url = ResourceLoader::makeLoaderURL( 02761 array_keys( $grpModules ), 02762 $this->getLanguage()->getCode(), 02763 $this->getSkin()->getSkinName(), 02764 $user, 02765 $version, 02766 ResourceLoader::inDebugMode(), 02767 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02768 $this->isPrintable(), 02769 $this->getRequest()->getBool( 'handheld' ), 02770 $extraQuery 02771 ); 02772 if ( $useESI && $wgResourceLoaderUseESI ) { 02773 $esi = Xml::element( 'esi:include', array( 'src' => $url ) ); 02774 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02775 $link = Html::inlineStyle( $esi ); 02776 } else { 02777 $link = Html::inlineScript( $esi ); 02778 } 02779 } else { 02780 // Automatically select style/script elements 02781 if ( $only === ResourceLoaderModule::TYPE_STYLES ) { 02782 $link = Html::linkedStyle( $url ); 02783 } elseif ( $loadCall ) { 02784 $link = Html::inlineScript( 02785 ResourceLoader::makeLoaderConditionalScript( 02786 Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) ) 02787 ) 02788 ); 02789 } else { 02790 $link = Html::linkedScript( $url ); 02791 02792 // For modules requested directly in the html via <link> or <script>, 02793 // tell mw.loader they are being loading to prevent duplicate requests. 02794 foreach ( $grpModules as $key => $module ) { 02795 // Don't output state=loading for the startup module.. 02796 if ( $key !== 'startup' ) { 02797 $links['states'][$key] = 'loading'; 02798 } 02799 } 02800 } 02801 } 02802 02803 if ( $group == 'noscript' ) { 02804 $links['html'] .= Html::rawElement( 'noscript', array(), $link ) . "\n"; 02805 } else { 02806 $links['html'] .= $link . "\n"; 02807 } 02808 } 02809 02810 return $links; 02811 } 02812 02818 protected static function getHtmlFromLoaderLinks( Array $links ) { 02819 $html = ''; 02820 $states = array(); 02821 foreach ( $links as $link ) { 02822 if ( !is_array( $link ) ) { 02823 $html .= $link; 02824 } else { 02825 $html .= $link['html']; 02826 $states += $link['states']; 02827 } 02828 } 02829 02830 if ( count( $states ) ) { 02831 $html = Html::inlineScript( 02832 ResourceLoader::makeLoaderConditionalScript( 02833 ResourceLoader::makeLoaderStateScript( $states ) 02834 ) 02835 ) . "\n" . $html; 02836 } 02837 02838 return $html; 02839 } 02840 02847 function getHeadScripts() { 02848 global $wgResourceLoaderExperimentalAsyncLoading; 02849 02850 // Startup - this will immediately load jquery and mediawiki modules 02851 $links = array(); 02852 $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true ); 02853 02854 // Load config before anything else 02855 $links[] = Html::inlineScript( 02856 ResourceLoader::makeLoaderConditionalScript( 02857 ResourceLoader::makeConfigSetScript( $this->getJSVars() ) 02858 ) 02859 ); 02860 02861 // Load embeddable private modules before any loader links 02862 // This needs to be TYPE_COMBINED so these modules are properly wrapped 02863 // in mw.loader.implement() calls and deferred until mw.user is available 02864 $embedScripts = array( 'user.options', 'user.tokens' ); 02865 $links[] = $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED ); 02866 02867 // Scripts and messages "only" requests marked for top inclusion 02868 // Messages should go first 02869 $links[] = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES ); 02870 $links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS ); 02871 02872 // Modules requests - let the client calculate dependencies and batch requests as it likes 02873 // Only load modules that have marked themselves for loading at the top 02874 $modules = $this->getModules( true, 'top' ); 02875 if ( $modules ) { 02876 $links[] = Html::inlineScript( 02877 ResourceLoader::makeLoaderConditionalScript( 02878 Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) 02879 ) 02880 ); 02881 } 02882 02883 if ( $wgResourceLoaderExperimentalAsyncLoading ) { 02884 $links[] = $this->getScriptsForBottomQueue( true ); 02885 } 02886 02887 return self::getHtmlFromLoaderLinks( $links ); 02888 } 02889 02899 function getScriptsForBottomQueue( $inHead ) { 02900 global $wgUseSiteJs, $wgAllowUserJs; 02901 02902 // Scripts and messages "only" requests marked for bottom inclusion 02903 // If we're in the <head>, use load() calls rather than <script src="..."> tags 02904 // Messages should go first 02905 $links = array(); 02906 $links[] = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), 02907 ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(), 02908 /* $loadCall = */ $inHead 02909 ); 02910 $links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), 02911 ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(), 02912 /* $loadCall = */ $inHead 02913 ); 02914 02915 // Modules requests - let the client calculate dependencies and batch requests as it likes 02916 // Only load modules that have marked themselves for loading at the bottom 02917 $modules = $this->getModules( true, 'bottom' ); 02918 if ( $modules ) { 02919 $links[] = Html::inlineScript( 02920 ResourceLoader::makeLoaderConditionalScript( 02921 Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) ) 02922 ) 02923 ); 02924 } 02925 02926 // Legacy Scripts 02927 $links[] = "\n" . $this->mScripts; 02928 02929 // Add site JS if enabled 02930 $links[] = $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS, 02931 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02932 ); 02933 02934 // Add user JS if enabled 02935 if ( $wgAllowUserJs && $this->getUser()->isLoggedIn() && $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) { 02936 # XXX: additional security check/prompt? 02937 // We're on a preview of a JS subpage 02938 // Exclude this page from the user module in case it's in there (bug 26283) 02939 $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false, 02940 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead 02941 ); 02942 // Load the previewed JS 02943 $links[] = Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n"; 02944 02945 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded 02946 // asynchronously and may arrive *after* the inline script here. So the previewed code 02947 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js... 02948 } else { 02949 // Include the user module normally, i.e., raw to avoid it being wrapped in a closure. 02950 $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, 02951 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02952 ); 02953 } 02954 02955 // Group JS is only enabled if site JS is enabled. 02956 $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED, 02957 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02958 ); 02959 02960 return self::getHtmlFromLoaderLinks( $links ); 02961 } 02962 02967 function getBottomScripts() { 02968 global $wgResourceLoaderExperimentalAsyncLoading; 02969 02970 // Optimise jQuery ready event cross-browser. 02971 // This also enforces $.isReady to be true at </body> which fixes the 02972 // mw.loader bug in Firefox with using document.write between </body> 02973 // and the DOMContentReady event (bug 47457). 02974 $html = Html::inlineScript( 'window.jQuery && jQuery.ready();' ); 02975 02976 if ( !$wgResourceLoaderExperimentalAsyncLoading ) { 02977 $html .= $this->getScriptsForBottomQueue( false ); 02978 } 02979 02980 return $html; 02981 } 02982 02989 public function getJsConfigVars() { 02990 return $this->mJsConfigVars; 02991 } 02992 02999 public function addJsConfigVars( $keys, $value = null ) { 03000 if ( is_array( $keys ) ) { 03001 foreach ( $keys as $key => $value ) { 03002 $this->mJsConfigVars[$key] = $value; 03003 } 03004 return; 03005 } 03006 03007 $this->mJsConfigVars[$keys] = $value; 03008 } 03009 03022 public function getJSVars() { 03023 global $wgContLang; 03024 03025 $curRevisionId = 0; 03026 $articleId = 0; 03027 $canonicalSpecialPageName = false; # bug 21115 03028 03029 $title = $this->getTitle(); 03030 $ns = $title->getNamespace(); 03031 $canonicalNamespace = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText(); 03032 03033 $sk = $this->getSkin(); 03034 // Get the relevant title so that AJAX features can use the correct page name 03035 // when making API requests from certain special pages (bug 34972). 03036 $relevantTitle = $sk->getRelevantTitle(); 03037 $relevantUser = $sk->getRelevantUser(); 03038 03039 if ( $ns == NS_SPECIAL ) { 03040 list( $canonicalSpecialPageName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); 03041 } elseif ( $this->canUseWikiPage() ) { 03042 $wikiPage = $this->getWikiPage(); 03043 $curRevisionId = $wikiPage->getLatest(); 03044 $articleId = $wikiPage->getId(); 03045 } 03046 03047 $lang = $title->getPageLanguage(); 03048 03049 // Pre-process information 03050 $separatorTransTable = $lang->separatorTransformTable(); 03051 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); 03052 $compactSeparatorTransTable = array( 03053 implode( "\t", array_keys( $separatorTransTable ) ), 03054 implode( "\t", $separatorTransTable ), 03055 ); 03056 $digitTransTable = $lang->digitTransformTable(); 03057 $digitTransTable = $digitTransTable ? $digitTransTable : array(); 03058 $compactDigitTransTable = array( 03059 implode( "\t", array_keys( $digitTransTable ) ), 03060 implode( "\t", $digitTransTable ), 03061 ); 03062 03063 $user = $this->getUser(); 03064 03065 $vars = array( 03066 'wgCanonicalNamespace' => $canonicalNamespace, 03067 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName, 03068 'wgNamespaceNumber' => $title->getNamespace(), 03069 'wgPageName' => $title->getPrefixedDBkey(), 03070 'wgTitle' => $title->getText(), 03071 'wgCurRevisionId' => $curRevisionId, 03072 'wgRevisionId' => (int)$this->getRevisionId(), 03073 'wgArticleId' => $articleId, 03074 'wgIsArticle' => $this->isArticle(), 03075 'wgIsRedirect' => $title->isRedirect(), 03076 'wgAction' => Action::getActionName( $this->getContext() ), 03077 'wgUserName' => $user->isAnon() ? null : $user->getName(), 03078 'wgUserGroups' => $user->getEffectiveGroups(), 03079 'wgCategories' => $this->getCategories(), 03080 'wgBreakFrames' => $this->getFrameOptions() == 'DENY', 03081 'wgPageContentLanguage' => $lang->getCode(), 03082 'wgPageContentModel' => $title->getContentModel(), 03083 'wgSeparatorTransformTable' => $compactSeparatorTransTable, 03084 'wgDigitTransformTable' => $compactDigitTransTable, 03085 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(), 03086 'wgMonthNames' => $lang->getMonthNamesArray(), 03087 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(), 03088 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(), 03089 ); 03090 if ( $user->isLoggedIn() ) { 03091 $vars['wgUserId'] = $user->getId(); 03092 $vars['wgUserEditCount'] = $user->getEditCount(); 03093 $userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() ); 03094 $vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null; 03095 // Get the revision ID of the oldest new message on the user's talk 03096 // page. This can be used for constructing new message alerts on 03097 // the client side. 03098 $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId(); 03099 } 03100 if ( $wgContLang->hasVariants() ) { 03101 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); 03102 } 03103 // Same test as SkinTemplate 03104 $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user ) && ( $title->exists() || $title->quickUserCan( 'create', $user ) ); 03105 foreach ( $title->getRestrictionTypes() as $type ) { 03106 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type ); 03107 } 03108 if ( $title->isMainPage() ) { 03109 $vars['wgIsMainPage'] = true; 03110 } 03111 if ( $this->mRedirectedFrom ) { 03112 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey(); 03113 } 03114 if ( $relevantUser ) { 03115 $vars['wgRelevantUserName'] = $relevantUser->getName(); 03116 } 03117 03118 // Allow extensions to add their custom variables to the mw.config map. 03119 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not 03120 // page-dependant but site-wide (without state). 03121 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. 03122 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) ); 03123 03124 // Merge in variables from addJsConfigVars last 03125 return array_merge( $vars, $this->getJsConfigVars() ); 03126 } 03127 03137 public function userCanPreview() { 03138 if ( $this->getRequest()->getVal( 'action' ) != 'submit' 03139 || !$this->getRequest()->wasPosted() 03140 || !$this->getUser()->matchEditToken( 03141 $this->getRequest()->getVal( 'wpEditToken' ) ) 03142 ) { 03143 return false; 03144 } 03145 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) { 03146 return false; 03147 } 03148 03149 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) ); 03150 } 03151 03155 public function getHeadLinksArray() { 03156 global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI, 03157 $wgSitename, $wgVersion, 03158 $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes, 03159 $wgDisableLangConversion, $wgCanonicalLanguageLinks, 03160 $wgRightsPage, $wgRightsUrl; 03161 03162 $tags = array(); 03163 03164 $canonicalUrl = $this->mCanonicalUrl; 03165 03166 $tags['meta-generator'] = Html::element( 'meta', array( 03167 'name' => 'generator', 03168 'content' => "MediaWiki $wgVersion", 03169 ) ); 03170 03171 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}"; 03172 if ( $p !== 'index,follow' ) { 03173 // http://www.robotstxt.org/wc/meta-user.html 03174 // Only show if it's different from the default robots policy 03175 $tags['meta-robots'] = Html::element( 'meta', array( 03176 'name' => 'robots', 03177 'content' => $p, 03178 ) ); 03179 } 03180 03181 foreach ( $this->mMetatags as $tag ) { 03182 if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) { 03183 $a = 'http-equiv'; 03184 $tag[0] = substr( $tag[0], 5 ); 03185 } else { 03186 $a = 'name'; 03187 } 03188 $tagName = "meta-{$tag[0]}"; 03189 if ( isset( $tags[$tagName] ) ) { 03190 $tagName .= $tag[1]; 03191 } 03192 $tags[$tagName] = Html::element( 'meta', 03193 array( 03194 $a => $tag[0], 03195 'content' => $tag[1] 03196 ) 03197 ); 03198 } 03199 03200 foreach ( $this->mLinktags as $tag ) { 03201 $tags[] = Html::element( 'link', $tag ); 03202 } 03203 03204 # Universal edit button 03205 if ( $wgUniversalEditButton && $this->isArticleRelated() ) { 03206 $user = $this->getUser(); 03207 if ( $this->getTitle()->quickUserCan( 'edit', $user ) 03208 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) { 03209 // Original UniversalEditButton 03210 $msg = $this->msg( 'edit' )->text(); 03211 $tags['universal-edit-button'] = Html::element( 'link', array( 03212 'rel' => 'alternate', 03213 'type' => 'application/x-wiki', 03214 'title' => $msg, 03215 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03216 ) ); 03217 // Alternate edit link 03218 $tags['alternative-edit'] = Html::element( 'link', array( 03219 'rel' => 'edit', 03220 'title' => $msg, 03221 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03222 ) ); 03223 } 03224 } 03225 03226 # Generally the order of the favicon and apple-touch-icon links 03227 # should not matter, but Konqueror (3.5.9 at least) incorrectly 03228 # uses whichever one appears later in the HTML source. Make sure 03229 # apple-touch-icon is specified first to avoid this. 03230 if ( $wgAppleTouchIcon !== false ) { 03231 $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) ); 03232 } 03233 03234 if ( $wgFavicon !== false ) { 03235 $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) ); 03236 } 03237 03238 # OpenSearch description link 03239 $tags['opensearch'] = Html::element( 'link', array( 03240 'rel' => 'search', 03241 'type' => 'application/opensearchdescription+xml', 03242 'href' => wfScript( 'opensearch_desc' ), 03243 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(), 03244 ) ); 03245 03246 if ( $wgEnableAPI ) { 03247 # Real Simple Discovery link, provides auto-discovery information 03248 # for the MediaWiki API (and potentially additional custom API 03249 # support such as WordPress or Twitter-compatible APIs for a 03250 # blogging extension, etc) 03251 $tags['rsd'] = Html::element( 'link', array( 03252 'rel' => 'EditURI', 03253 'type' => 'application/rsd+xml', 03254 // Output a protocol-relative URL here if $wgServer is protocol-relative 03255 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though 03256 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ), 03257 ) ); 03258 } 03259 03260 # Language variants 03261 if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) { 03262 $lang = $this->getTitle()->getPageLanguage(); 03263 if ( $lang->hasVariants() ) { 03264 03265 $urlvar = $lang->getURLVariant(); 03266 03267 if ( !$urlvar ) { 03268 $variants = $lang->getVariants(); 03269 foreach ( $variants as $_v ) { 03270 $tags["variant-$_v"] = Html::element( 'link', array( 03271 'rel' => 'alternate', 03272 'hreflang' => wfBCP47( $_v ), 03273 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) ) 03274 ); 03275 } 03276 } else { 03277 $canonicalUrl = $this->getTitle()->getLocalURL(); 03278 } 03279 } 03280 } 03281 03282 # Copyright 03283 $copyright = ''; 03284 if ( $wgRightsPage ) { 03285 $copy = Title::newFromText( $wgRightsPage ); 03286 03287 if ( $copy ) { 03288 $copyright = $copy->getLocalURL(); 03289 } 03290 } 03291 03292 if ( !$copyright && $wgRightsUrl ) { 03293 $copyright = $wgRightsUrl; 03294 } 03295 03296 if ( $copyright ) { 03297 $tags['copyright'] = Html::element( 'link', array( 03298 'rel' => 'copyright', 03299 'href' => $copyright ) 03300 ); 03301 } 03302 03303 # Feeds 03304 if ( $wgFeed ) { 03305 foreach ( $this->getSyndicationLinks() as $format => $link ) { 03306 # Use the page name for the title. In principle, this could 03307 # lead to issues with having the same name for different feeds 03308 # corresponding to the same page, but we can't avoid that at 03309 # this low a level. 03310 03311 $tags[] = $this->feedLink( 03312 $format, 03313 $link, 03314 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep) 03315 $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text() 03316 ); 03317 } 03318 03319 # Recent changes feed should appear on every page (except recentchanges, 03320 # that would be redundant). Put it after the per-page feed to avoid 03321 # changing existing behavior. It's still available, probably via a 03322 # menu in your browser. Some sites might have a different feed they'd 03323 # like to promote instead of the RC feed (maybe like a "Recent New Articles" 03324 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined. 03325 # If so, use it instead. 03326 if ( $wgOverrideSiteFeed ) { 03327 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) { 03328 // Note, this->feedLink escapes the url. 03329 $tags[] = $this->feedLink( 03330 $type, 03331 $feedUrl, 03332 $this->msg( "site-{$type}-feed", $wgSitename )->text() 03333 ); 03334 } 03335 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) { 03336 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' ); 03337 foreach ( $wgAdvertisedFeedTypes as $format ) { 03338 $tags[] = $this->feedLink( 03339 $format, 03340 $rctitle->getLocalURL( array( 'feed' => $format ) ), 03341 $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'. 03342 ); 03343 } 03344 } 03345 } 03346 03347 # Canonical URL 03348 global $wgEnableCanonicalServerLink; 03349 if ( $wgEnableCanonicalServerLink ) { 03350 if ( $canonicalUrl !== false ) { 03351 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL ); 03352 } else { 03353 $reqUrl = $this->getRequest()->getRequestURL(); 03354 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL ); 03355 } 03356 } 03357 if ( $canonicalUrl !== false ) { 03358 $tags[] = Html::element( 'link', array( 03359 'rel' => 'canonical', 03360 'href' => $canonicalUrl 03361 ) ); 03362 } 03363 03364 return $tags; 03365 } 03366 03370 public function getHeadLinks() { 03371 return implode( "\n", $this->getHeadLinksArray() ); 03372 } 03373 03382 private function feedLink( $type, $url, $text ) { 03383 return Html::element( 'link', array( 03384 'rel' => 'alternate', 03385 'type' => "application/$type+xml", 03386 'title' => $text, 03387 'href' => $url ) 03388 ); 03389 } 03390 03400 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) { 03401 $options = array(); 03402 // Even though we expect the media type to be lowercase, but here we 03403 // force it to lowercase to be safe. 03404 if ( $media ) { 03405 $options['media'] = $media; 03406 } 03407 if ( $condition ) { 03408 $options['condition'] = $condition; 03409 } 03410 if ( $dir ) { 03411 $options['dir'] = $dir; 03412 } 03413 $this->styles[$style] = $options; 03414 } 03415 03421 public function addInlineStyle( $style_css, $flip = 'noflip' ) { 03422 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) { 03423 # If wanted, and the interface is right-to-left, flip the CSS 03424 $style_css = CSSJanus::transform( $style_css, true, false ); 03425 } 03426 $this->mInlineStyles .= Html::inlineStyle( $style_css ) . "\n"; 03427 } 03428 03435 public function buildCssLinks() { 03436 global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, $wgContLang; 03437 03438 $this->getSkin()->setupSkinUserCss( $this ); 03439 03440 // Add ResourceLoader styles 03441 // Split the styles into these groups 03442 $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() ); 03443 $links = array(); 03444 $otherTags = ''; // Tags to append after the normal <link> tags 03445 $resourceLoader = $this->getResourceLoader(); 03446 03447 $moduleStyles = $this->getModuleStyles(); 03448 03449 // Per-site custom styles 03450 $moduleStyles[] = 'site'; 03451 $moduleStyles[] = 'noscript'; 03452 $moduleStyles[] = 'user.groups'; 03453 03454 // Per-user custom styles 03455 if ( $wgAllowUserCss && $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) { 03456 // We're on a preview of a CSS subpage 03457 // Exclude this page from the user module in case it's in there (bug 26283) 03458 $link = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false, 03459 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ) 03460 ); 03461 $otherTags .= $link['html']; 03462 03463 // Load the previewed CSS 03464 // If needed, Janus it first. This is user-supplied CSS, so it's 03465 // assumed to be right for the content language directionality. 03466 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' ); 03467 if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) { 03468 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false ); 03469 } 03470 $otherTags .= Html::inlineStyle( $previewedCSS ) . "\n"; 03471 } else { 03472 // Load the user styles normally 03473 $moduleStyles[] = 'user'; 03474 } 03475 03476 // Per-user preference styles 03477 $moduleStyles[] = 'user.cssprefs'; 03478 03479 foreach ( $moduleStyles as $name ) { 03480 $module = $resourceLoader->getModule( $name ); 03481 if ( !$module ) { 03482 continue; 03483 } 03484 $group = $module->getGroup(); 03485 // Modules in groups different than the ones listed on top (see $styles assignment) 03486 // will be placed in the "other" group 03487 $styles[ isset( $styles[$group] ) ? $group : 'other' ][] = $name; 03488 } 03489 03490 // We want site, private and user styles to override dynamically added styles from modules, but we want 03491 // dynamically added styles to override statically added styles from other modules. So the order 03492 // has to be other, dynamic, site, private, user 03493 // Add statically added styles for other modules 03494 $links[] = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES ); 03495 // Add normal styles added through addStyle()/addInlineStyle() here 03496 $links[] = implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles; 03497 // Add marker tag to mark the place where the client-side loader should inject dynamic styles 03498 // We use a <meta> tag with a made-up name for this because that's valid HTML 03499 $links[] = Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n"; 03500 03501 // Add site, private and user styles 03502 // 'private' at present only contains user.options, so put that before 'user' 03503 // Any future private modules will likely have a similar user-specific character 03504 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) { 03505 $links[] = $this->makeResourceLoaderLink( $styles[$group], 03506 ResourceLoaderModule::TYPE_STYLES 03507 ); 03508 } 03509 03510 // Add stuff in $otherTags (previewed user CSS if applicable) 03511 return self::getHtmlFromLoaderLinks( $links ) . $otherTags; 03512 } 03513 03517 public function buildCssLinksArray() { 03518 $links = array(); 03519 03520 // Add any extension CSS 03521 foreach ( $this->mExtStyles as $url ) { 03522 $this->addStyle( $url ); 03523 } 03524 $this->mExtStyles = array(); 03525 03526 foreach ( $this->styles as $file => $options ) { 03527 $link = $this->styleLink( $file, $options ); 03528 if ( $link ) { 03529 $links[$file] = $link; 03530 } 03531 } 03532 return $links; 03533 } 03534 03542 protected function styleLink( $style, $options ) { 03543 if ( isset( $options['dir'] ) ) { 03544 if ( $this->getLanguage()->getDir() != $options['dir'] ) { 03545 return ''; 03546 } 03547 } 03548 03549 if ( isset( $options['media'] ) ) { 03550 $media = self::transformCssMedia( $options['media'] ); 03551 if ( is_null( $media ) ) { 03552 return ''; 03553 } 03554 } else { 03555 $media = 'all'; 03556 } 03557 03558 if ( substr( $style, 0, 1 ) == '/' || 03559 substr( $style, 0, 5 ) == 'http:' || 03560 substr( $style, 0, 6 ) == 'https:' ) { 03561 $url = $style; 03562 } else { 03563 global $wgStylePath, $wgStyleVersion; 03564 $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion; 03565 } 03566 03567 $link = Html::linkedStyle( $url, $media ); 03568 03569 if ( isset( $options['condition'] ) ) { 03570 $condition = htmlspecialchars( $options['condition'] ); 03571 $link = "<!--[if $condition]>$link<![endif]-->"; 03572 } 03573 return $link; 03574 } 03575 03583 public static function transformCssMedia( $media ) { 03584 global $wgRequest; 03585 03586 // http://www.w3.org/TR/css3-mediaqueries/#syntax 03587 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i'; 03588 03589 // Switch in on-screen display for media testing 03590 $switches = array( 03591 'printable' => 'print', 03592 'handheld' => 'handheld', 03593 ); 03594 foreach ( $switches as $switch => $targetMedia ) { 03595 if ( $wgRequest->getBool( $switch ) ) { 03596 if ( $media == $targetMedia ) { 03597 $media = ''; 03598 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) { 03599 // This regex will not attempt to understand a comma-separated media_query_list 03600 // 03601 // Example supported values for $media: 'screen', 'only screen', 'screen and (min-width: 982px)' ), 03602 // Example NOT supported value for $media: '3d-glasses, screen, print and resolution > 90dpi' 03603 // 03604 // If it's a print request, we never want any kind of screen stylesheets 03605 // If it's a handheld request (currently the only other choice with a switch), 03606 // we don't want simple 'screen' but we might want screen queries that 03607 // have a max-width or something, so we'll pass all others on and let the 03608 // client do the query. 03609 if ( $targetMedia == 'print' || $media == 'screen' ) { 03610 return null; 03611 } 03612 } 03613 } 03614 } 03615 03616 return $media; 03617 } 03618 03625 public function addWikiMsg( /*...*/ ) { 03626 $args = func_get_args(); 03627 $name = array_shift( $args ); 03628 $this->addWikiMsgArray( $name, $args ); 03629 } 03630 03639 public function addWikiMsgArray( $name, $args ) { 03640 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() ); 03641 } 03642 03666 public function wrapWikiMsg( $wrap /*, ...*/ ) { 03667 $msgSpecs = func_get_args(); 03668 array_shift( $msgSpecs ); 03669 $msgSpecs = array_values( $msgSpecs ); 03670 $s = $wrap; 03671 foreach ( $msgSpecs as $n => $spec ) { 03672 if ( is_array( $spec ) ) { 03673 $args = $spec; 03674 $name = array_shift( $args ); 03675 if ( isset( $args['options'] ) ) { 03676 unset( $args['options'] ); 03677 wfDeprecated( 03678 'Adding "options" to ' . __METHOD__ . ' is no longer supported', 03679 '1.20' 03680 ); 03681 } 03682 } else { 03683 $args = array(); 03684 $name = $spec; 03685 } 03686 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s ); 03687 } 03688 $this->addWikiText( $s ); 03689 } 03690 03700 public function includeJQuery( $modules = array() ) { 03701 return array(); 03702 } 03703 03709 public function enableTOC( $flag = true ) { 03710 $this->mEnableTOC = $flag; 03711 } 03712 03717 public function isTOCEnabled() { 03718 return $this->mEnableTOC; 03719 } 03720 03726 public function enableSectionEditLinks( $flag = true ) { 03727 $this->mEnableSectionEditLinks = $flag; 03728 } 03729 03734 public function sectionEditLinksEnabled() { 03735 return $this->mEnableSectionEditLinks; 03736 } 03737 }