MediaWiki
REL1_21
|
00001 <?php 00038 class OutputPage extends ContextSource { 00040 var $mMetatags = array(); 00041 00043 var $mKeywords = array(); 00044 00045 var $mLinktags = array(); 00046 var $mCanonicalUrl = false; 00047 00049 var $mExtStyles = array(); 00050 00052 var $mPagetitle = ''; 00053 00055 var $mBodytext = ''; 00056 00062 public $mDebugtext = ''; 00063 00065 var $mHTMLtitle = ''; 00066 00068 var $mIsarticle = false; 00069 00074 var $mIsArticleRelated = true; 00075 00080 var $mPrintable = false; 00081 00088 private $mSubtitle = array(); 00089 00090 var $mRedirect = ''; 00091 var $mStatusCode; 00092 00097 var $mLastModified = ''; 00098 00109 var $mETag = false; 00110 00111 var $mCategoryLinks = array(); 00112 var $mCategories = array(); 00113 00115 var $mLanguageLinks = array(); 00116 00123 var $mScripts = ''; 00124 00128 var $mInlineStyles = ''; 00129 00130 // 00131 var $mLinkColours; 00132 00137 var $mPageLinkTitle = ''; 00138 00140 var $mHeadItems = array(); 00141 00142 // @todo FIXME: Next variables probably comes from the resource loader 00143 var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array(); 00144 var $mResourceLoader; 00145 var $mJsConfigVars = array(); 00146 00148 var $mInlineMsg = array(); 00149 00150 var $mTemplateIds = array(); 00151 var $mImageTimeKeys = array(); 00152 00153 var $mRedirectCode = ''; 00154 00155 var $mFeedLinksAppendQuery = null; 00156 00157 # What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page? 00158 # @see ResourceLoaderModule::$origin 00159 # ResourceLoaderModule::ORIGIN_ALL is assumed unless overridden; 00160 protected $mAllowedModules = array( 00161 ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL, 00162 ); 00163 00168 var $mDoNothing = false; 00169 00170 // Parser related. 00171 var $mContainsOldMagic = 0, $mContainsNewMagic = 0; 00172 00177 protected $mParserOptions = null; 00178 00185 var $mFeedLinks = array(); 00186 00187 // Gwicke work on squid caching? Roughly from 2003. 00188 var $mEnableClientCache = true; 00189 00194 var $mArticleBodyOnly = false; 00195 00196 var $mNewSectionLink = false; 00197 var $mHideNewSectionLink = false; 00198 00204 var $mNoGallery = false; 00205 00206 // should be private. 00207 var $mPageTitleActionText = ''; 00208 var $mParseWarnings = array(); 00209 00210 // Cache stuff. Looks like mEnableClientCache 00211 var $mSquidMaxage = 0; 00212 00213 // @todo document 00214 var $mPreventClickjacking = true; 00215 00217 var $mRevisionId = null; 00218 private $mRevisionTimestamp = null; 00219 00220 var $mFileVersion = null; 00221 00230 var $styles = array(); 00231 00235 protected $mJQueryDone = false; 00236 00237 private $mIndexPolicy = 'index'; 00238 private $mFollowPolicy = 'follow'; 00239 private $mVaryHeader = array( 00240 'Accept-Encoding' => array( 'list-contains=gzip' ), 00241 ); 00242 00249 private $mRedirectedFrom = null; 00250 00254 private $mProperties = array(); 00255 00261 function __construct( IContextSource $context = null ) { 00262 if ( $context === null ) { 00263 # Extensions should use `new RequestContext` instead of `new OutputPage` now. 00264 wfDeprecated( __METHOD__, '1.18' ); 00265 } else { 00266 $this->setContext( $context ); 00267 } 00268 } 00269 00276 public function redirect( $url, $responsecode = '302' ) { 00277 # Strip newlines as a paranoia check for header injection in PHP<5.1.2 00278 $this->mRedirect = str_replace( "\n", '', $url ); 00279 $this->mRedirectCode = $responsecode; 00280 } 00281 00287 public function getRedirect() { 00288 return $this->mRedirect; 00289 } 00290 00296 public function setStatusCode( $statusCode ) { 00297 $this->mStatusCode = $statusCode; 00298 } 00299 00307 function addMeta( $name, $val ) { 00308 array_push( $this->mMetatags, array( $name, $val ) ); 00309 } 00310 00316 function addKeyword( $text ) { 00317 if( is_array( $text ) ) { 00318 $this->mKeywords = array_merge( $this->mKeywords, $text ); 00319 } else { 00320 array_push( $this->mKeywords, $text ); 00321 } 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 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) ); 00419 } 00420 00426 public function addInlineScript( $script ) { 00427 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n"; 00428 } 00429 00435 function getScript() { 00436 return $this->mScripts . $this->getHeadItems(); 00437 } 00438 00447 protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ) { 00448 $resourceLoader = $this->getResourceLoader(); 00449 $filteredModules = array(); 00450 foreach( $modules as $val ) { 00451 $module = $resourceLoader->getModule( $val ); 00452 if( $module instanceof ResourceLoaderModule 00453 && $module->getOrigin() <= $this->getAllowedModules( $type ) 00454 && ( is_null( $position ) || $module->getPosition() == $position ) ) 00455 { 00456 $filteredModules[] = $val; 00457 } 00458 } 00459 return $filteredModules; 00460 } 00461 00470 public function getModules( $filter = false, $position = null, $param = 'mModules' ) { 00471 $modules = array_values( array_unique( $this->$param ) ); 00472 return $filter 00473 ? $this->filterModules( $modules, $position ) 00474 : $modules; 00475 } 00476 00484 public function addModules( $modules ) { 00485 $this->mModules = array_merge( $this->mModules, (array)$modules ); 00486 } 00487 00496 public function getModuleScripts( $filter = false, $position = null ) { 00497 return $this->getModules( $filter, $position, 'mModuleScripts' ); 00498 } 00499 00507 public function addModuleScripts( $modules ) { 00508 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules ); 00509 } 00510 00519 public function getModuleStyles( $filter = false, $position = null ) { 00520 return $this->getModules( $filter, $position, 'mModuleStyles' ); 00521 } 00522 00532 public function addModuleStyles( $modules ) { 00533 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules ); 00534 } 00535 00544 public function getModuleMessages( $filter = false, $position = null ) { 00545 return $this->getModules( $filter, $position, 'mModuleMessages' ); 00546 } 00547 00555 public function addModuleMessages( $modules ) { 00556 $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules ); 00557 } 00558 00564 function getHeadItemsArray() { 00565 return $this->mHeadItems; 00566 } 00567 00573 function getHeadItems() { 00574 $s = ''; 00575 foreach ( $this->mHeadItems as $item ) { 00576 $s .= $item; 00577 } 00578 return $s; 00579 } 00580 00587 public function addHeadItem( $name, $value ) { 00588 $this->mHeadItems[$name] = $value; 00589 } 00590 00597 public function hasHeadItem( $name ) { 00598 return isset( $this->mHeadItems[$name] ); 00599 } 00600 00606 function setETag( $tag ) { 00607 $this->mETag = $tag; 00608 } 00609 00617 public function setArticleBodyOnly( $only ) { 00618 $this->mArticleBodyOnly = $only; 00619 } 00620 00626 public function getArticleBodyOnly() { 00627 return $this->mArticleBodyOnly; 00628 } 00629 00637 public function setProperty( $name, $value ) { 00638 $this->mProperties[$name] = $value; 00639 } 00640 00648 public function getProperty( $name ) { 00649 if ( isset( $this->mProperties[$name] ) ) { 00650 return $this->mProperties[$name]; 00651 } else { 00652 return null; 00653 } 00654 } 00655 00667 public function checkLastModified( $timestamp ) { 00668 global $wgCachePages, $wgCacheEpoch; 00669 00670 if ( !$timestamp || $timestamp == '19700101000000' ) { 00671 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" ); 00672 return false; 00673 } 00674 if( !$wgCachePages ) { 00675 wfDebug( __METHOD__ . ": CACHE DISABLED\n", false ); 00676 return false; 00677 } 00678 if( $this->getUser()->getOption( 'nocache' ) ) { 00679 wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false ); 00680 return false; 00681 } 00682 00683 $timestamp = wfTimestamp( TS_MW, $timestamp ); 00684 $modifiedTimes = array( 00685 'page' => $timestamp, 00686 'user' => $this->getUser()->getTouched(), 00687 'epoch' => $wgCacheEpoch 00688 ); 00689 wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) ); 00690 00691 $maxModified = max( $modifiedTimes ); 00692 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified ); 00693 00694 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' ); 00695 if ( $clientHeader === false ) { 00696 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false ); 00697 return false; 00698 } 00699 00700 # IE sends sizes after the date like this: 00701 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 00702 # this breaks strtotime(). 00703 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader ); 00704 00705 wfSuppressWarnings(); // E_STRICT system time bitching 00706 $clientHeaderTime = strtotime( $clientHeader ); 00707 wfRestoreWarnings(); 00708 if ( !$clientHeaderTime ) { 00709 wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" ); 00710 return false; 00711 } 00712 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime ); 00713 00714 # Make debug info 00715 $info = ''; 00716 foreach ( $modifiedTimes as $name => $value ) { 00717 if ( $info !== '' ) { 00718 $info .= ', '; 00719 } 00720 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value ); 00721 } 00722 00723 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " . 00724 wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false ); 00725 wfDebug( __METHOD__ . ": effective Last-Modified: " . 00726 wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false ); 00727 if( $clientHeaderTime < $maxModified ) { 00728 wfDebug( __METHOD__ . ": STALE, $info\n", false ); 00729 return false; 00730 } 00731 00732 # Not modified 00733 # Give a 304 response code and disable body output 00734 wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false ); 00735 ini_set( 'zlib.output_compression', 0 ); 00736 $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" ); 00737 $this->sendCacheControl(); 00738 $this->disable(); 00739 00740 // Don't output a compressed blob when using ob_gzhandler; 00741 // it's technically against HTTP spec and seems to confuse 00742 // Firefox when the response gets split over two packets. 00743 wfClearOutputBuffers(); 00744 00745 return true; 00746 } 00747 00754 public function setLastModified( $timestamp ) { 00755 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp ); 00756 } 00757 00766 public function setRobotPolicy( $policy ) { 00767 $policy = Article::formatRobotPolicy( $policy ); 00768 00769 if( isset( $policy['index'] ) ) { 00770 $this->setIndexPolicy( $policy['index'] ); 00771 } 00772 if( isset( $policy['follow'] ) ) { 00773 $this->setFollowPolicy( $policy['follow'] ); 00774 } 00775 } 00776 00784 public function setIndexPolicy( $policy ) { 00785 $policy = trim( $policy ); 00786 if( in_array( $policy, array( 'index', 'noindex' ) ) ) { 00787 $this->mIndexPolicy = $policy; 00788 } 00789 } 00790 00798 public function setFollowPolicy( $policy ) { 00799 $policy = trim( $policy ); 00800 if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) { 00801 $this->mFollowPolicy = $policy; 00802 } 00803 } 00804 00811 public function setPageTitleActionText( $text ) { 00812 $this->mPageTitleActionText = $text; 00813 } 00814 00820 public function getPageTitleActionText() { 00821 if ( isset( $this->mPageTitleActionText ) ) { 00822 return $this->mPageTitleActionText; 00823 } 00824 return ''; 00825 } 00826 00833 public function setHTMLTitle( $name ) { 00834 if ( $name instanceof Message ) { 00835 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text(); 00836 } else { 00837 $this->mHTMLtitle = $name; 00838 } 00839 } 00840 00846 public function getHTMLTitle() { 00847 return $this->mHTMLtitle; 00848 } 00849 00855 public function setRedirectedFrom( $t ) { 00856 $this->mRedirectedFrom = $t; 00857 } 00858 00867 public function setPageTitle( $name ) { 00868 if ( $name instanceof Message ) { 00869 $name = $name->setContext( $this->getContext() )->text(); 00870 } 00871 00872 # change "<script>foo&bar</script>" to "<script>foo&bar</script>" 00873 # but leave "<i>foobar</i>" alone 00874 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) ); 00875 $this->mPagetitle = $nameWithTags; 00876 00877 # change "<i>foo&bar</i>" to "foo&bar" 00878 $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) ); 00879 } 00880 00886 public function getPageTitle() { 00887 return $this->mPagetitle; 00888 } 00889 00895 public function setTitle( Title $t ) { 00896 $this->getContext()->setTitle( $t ); 00897 } 00898 00904 public function setSubtitle( $str ) { 00905 $this->clearSubtitle(); 00906 $this->addSubtitle( $str ); 00907 } 00908 00915 public function appendSubtitle( $str ) { 00916 $this->addSubtitle( $str ); 00917 } 00918 00924 public function addSubtitle( $str ) { 00925 if ( $str instanceof Message ) { 00926 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse(); 00927 } else { 00928 $this->mSubtitle[] = $str; 00929 } 00930 } 00931 00937 public function addBacklinkSubtitle( Title $title ) { 00938 $query = array(); 00939 if ( $title->isRedirect() ) { 00940 $query['redirect'] = 'no'; 00941 } 00942 $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) ); 00943 } 00944 00948 public function clearSubtitle() { 00949 $this->mSubtitle = array(); 00950 } 00951 00957 public function getSubtitle() { 00958 return implode( "<br />\n\t\t\t\t", $this->mSubtitle ); 00959 } 00960 00965 public function setPrintable() { 00966 $this->mPrintable = true; 00967 } 00968 00974 public function isPrintable() { 00975 return $this->mPrintable; 00976 } 00977 00981 public function disable() { 00982 $this->mDoNothing = true; 00983 } 00984 00990 public function isDisabled() { 00991 return $this->mDoNothing; 00992 } 00993 00999 public function showNewSectionLink() { 01000 return $this->mNewSectionLink; 01001 } 01002 01008 public function forceHideNewSectionLink() { 01009 return $this->mHideNewSectionLink; 01010 } 01011 01020 public function setSyndicated( $show = true ) { 01021 if ( $show ) { 01022 $this->setFeedAppendQuery( false ); 01023 } else { 01024 $this->mFeedLinks = array(); 01025 } 01026 } 01027 01037 public function setFeedAppendQuery( $val ) { 01038 global $wgAdvertisedFeedTypes; 01039 01040 $this->mFeedLinks = array(); 01041 01042 foreach ( $wgAdvertisedFeedTypes as $type ) { 01043 $query = "feed=$type"; 01044 if ( is_string( $val ) ) { 01045 $query .= '&' . $val; 01046 } 01047 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query ); 01048 } 01049 } 01050 01057 public function addFeedLink( $format, $href ) { 01058 global $wgAdvertisedFeedTypes; 01059 01060 if ( in_array( $format, $wgAdvertisedFeedTypes ) ) { 01061 $this->mFeedLinks[$format] = $href; 01062 } 01063 } 01064 01069 public function isSyndicated() { 01070 return count( $this->mFeedLinks ) > 0; 01071 } 01072 01077 public function getSyndicationLinks() { 01078 return $this->mFeedLinks; 01079 } 01080 01086 public function getFeedAppendQuery() { 01087 return $this->mFeedLinksAppendQuery; 01088 } 01089 01097 public function setArticleFlag( $v ) { 01098 $this->mIsarticle = $v; 01099 if ( $v ) { 01100 $this->mIsArticleRelated = $v; 01101 } 01102 } 01103 01110 public function isArticle() { 01111 return $this->mIsarticle; 01112 } 01113 01120 public function setArticleRelated( $v ) { 01121 $this->mIsArticleRelated = $v; 01122 if ( !$v ) { 01123 $this->mIsarticle = false; 01124 } 01125 } 01126 01132 public function isArticleRelated() { 01133 return $this->mIsArticleRelated; 01134 } 01135 01142 public function addLanguageLinks( $newLinkArray ) { 01143 $this->mLanguageLinks += $newLinkArray; 01144 } 01145 01152 public function setLanguageLinks( $newLinkArray ) { 01153 $this->mLanguageLinks = $newLinkArray; 01154 } 01155 01161 public function getLanguageLinks() { 01162 return $this->mLanguageLinks; 01163 } 01164 01170 public function addCategoryLinks( $categories ) { 01171 global $wgContLang; 01172 01173 if ( !is_array( $categories ) || count( $categories ) == 0 ) { 01174 return; 01175 } 01176 01177 # Add the links to a LinkBatch 01178 $arr = array( NS_CATEGORY => $categories ); 01179 $lb = new LinkBatch; 01180 $lb->setArray( $arr ); 01181 01182 # Fetch existence plus the hiddencat property 01183 $dbr = wfGetDB( DB_SLAVE ); 01184 $res = $dbr->select( array( 'page', 'page_props' ), 01185 array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ), 01186 $lb->constructSet( 'page', $dbr ), 01187 __METHOD__, 01188 array(), 01189 array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) ) 01190 ); 01191 01192 # Add the results to the link cache 01193 $lb->addResultToCache( LinkCache::singleton(), $res ); 01194 01195 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+ 01196 $categories = array_combine( 01197 array_keys( $categories ), 01198 array_fill( 0, count( $categories ), 'normal' ) 01199 ); 01200 01201 # Mark hidden categories 01202 foreach ( $res as $row ) { 01203 if ( isset( $row->pp_value ) ) { 01204 $categories[$row->page_title] = 'hidden'; 01205 } 01206 } 01207 01208 # Add the remaining categories to the skin 01209 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) { 01210 foreach ( $categories as $category => $type ) { 01211 $origcategory = $category; 01212 $title = Title::makeTitleSafe( NS_CATEGORY, $category ); 01213 $wgContLang->findVariantLink( $category, $title, true ); 01214 if ( $category != $origcategory ) { 01215 if ( array_key_exists( $category, $categories ) ) { 01216 continue; 01217 } 01218 } 01219 $text = $wgContLang->convertHtml( $title->getText() ); 01220 $this->mCategories[] = $title->getText(); 01221 $this->mCategoryLinks[$type][] = Linker::link( $title, $text ); 01222 } 01223 } 01224 } 01225 01231 public function setCategoryLinks( $categories ) { 01232 $this->mCategoryLinks = array(); 01233 $this->addCategoryLinks( $categories ); 01234 } 01235 01244 public function getCategoryLinks() { 01245 return $this->mCategoryLinks; 01246 } 01247 01253 public function getCategories() { 01254 return $this->mCategories; 01255 } 01256 01261 public function disallowUserJs() { 01262 $this->reduceAllowedModules( 01263 ResourceLoaderModule::TYPE_SCRIPTS, 01264 ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL 01265 ); 01266 } 01267 01275 public function isUserJsAllowed() { 01276 wfDeprecated( __METHOD__, '1.18' ); 01277 return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL; 01278 } 01279 01286 public function getAllowedModules( $type ) { 01287 if( $type == ResourceLoaderModule::TYPE_COMBINED ) { 01288 return min( array_values( $this->mAllowedModules ) ); 01289 } else { 01290 return isset( $this->mAllowedModules[$type] ) 01291 ? $this->mAllowedModules[$type] 01292 : ResourceLoaderModule::ORIGIN_ALL; 01293 } 01294 } 01295 01301 public function setAllowedModules( $type, $level ) { 01302 $this->mAllowedModules[$type] = $level; 01303 } 01304 01310 public function reduceAllowedModules( $type, $level ) { 01311 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level ); 01312 } 01313 01319 public function prependHTML( $text ) { 01320 $this->mBodytext = $text . $this->mBodytext; 01321 } 01322 01328 public function addHTML( $text ) { 01329 $this->mBodytext .= $text; 01330 } 01331 01341 public function addElement( $element, $attribs = array(), $contents = '' ) { 01342 $this->addHTML( Html::element( $element, $attribs, $contents ) ); 01343 } 01344 01348 public function clearHTML() { 01349 $this->mBodytext = ''; 01350 } 01351 01357 public function getHTML() { 01358 return $this->mBodytext; 01359 } 01360 01368 public function parserOptions( $options = null ) { 01369 if ( !$this->mParserOptions ) { 01370 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() ); 01371 $this->mParserOptions->setEditSection( false ); 01372 } 01373 return wfSetVar( $this->mParserOptions, $options ); 01374 } 01375 01383 public function setRevisionId( $revid ) { 01384 $val = is_null( $revid ) ? null : intval( $revid ); 01385 return wfSetVar( $this->mRevisionId, $val ); 01386 } 01387 01393 public function getRevisionId() { 01394 return $this->mRevisionId; 01395 } 01396 01404 public function setRevisionTimestamp( $timestamp) { 01405 return wfSetVar( $this->mRevisionTimestamp, $timestamp ); 01406 } 01407 01414 public function getRevisionTimestamp() { 01415 return $this->mRevisionTimestamp; 01416 } 01417 01424 public function setFileVersion( $file ) { 01425 $val = null; 01426 if ( $file instanceof File && $file->exists() ) { 01427 $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ); 01428 } 01429 return wfSetVar( $this->mFileVersion, $val, true ); 01430 } 01431 01437 public function getFileVersion() { 01438 return $this->mFileVersion; 01439 } 01440 01447 public function getTemplateIds() { 01448 return $this->mTemplateIds; 01449 } 01450 01457 public function getFileSearchOptions() { 01458 return $this->mImageTimeKeys; 01459 } 01460 01469 public function addWikiText( $text, $linestart = true, $interface = true ) { 01470 $title = $this->getTitle(); // Work around E_STRICT 01471 if ( !$title ) { 01472 throw new MWException( 'Title is null' ); 01473 } 01474 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface ); 01475 } 01476 01484 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) { 01485 $this->addWikiTextTitle( $text, $title, $linestart ); 01486 } 01487 01495 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) { 01496 $this->addWikiTextTitle( $text, $title, $linestart, true ); 01497 } 01498 01505 public function addWikiTextTidy( $text, $linestart = true ) { 01506 $title = $this->getTitle(); 01507 $this->addWikiTextTitleTidy( $text, $title, $linestart ); 01508 } 01509 01520 public function addWikiTextTitle( $text, Title $title, $linestart, $tidy = false, $interface = false ) { 01521 global $wgParser; 01522 01523 wfProfileIn( __METHOD__ ); 01524 01525 $popts = $this->parserOptions(); 01526 $oldTidy = $popts->setTidy( $tidy ); 01527 $popts->setInterfaceMessage( (bool) $interface ); 01528 01529 $parserOutput = $wgParser->parse( 01530 $text, $title, $popts, 01531 $linestart, true, $this->mRevisionId 01532 ); 01533 01534 $popts->setTidy( $oldTidy ); 01535 01536 $this->addParserOutput( $parserOutput ); 01537 01538 wfProfileOut( __METHOD__ ); 01539 } 01540 01546 public function addParserOutputNoText( &$parserOutput ) { 01547 $this->mLanguageLinks += $parserOutput->getLanguageLinks(); 01548 $this->addCategoryLinks( $parserOutput->getCategories() ); 01549 $this->mNewSectionLink = $parserOutput->getNewSection(); 01550 $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); 01551 01552 $this->mParseWarnings = $parserOutput->getWarnings(); 01553 if ( !$parserOutput->isCacheable() ) { 01554 $this->enableClientCache( false ); 01555 } 01556 $this->mNoGallery = $parserOutput->getNoGallery(); 01557 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() ); 01558 $this->addModules( $parserOutput->getModules() ); 01559 $this->addModuleScripts( $parserOutput->getModuleScripts() ); 01560 $this->addModuleStyles( $parserOutput->getModuleStyles() ); 01561 $this->addModuleMessages( $parserOutput->getModuleMessages() ); 01562 01563 // Template versioning... 01564 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) { 01565 if ( isset( $this->mTemplateIds[$ns] ) ) { 01566 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns]; 01567 } else { 01568 $this->mTemplateIds[$ns] = $dbks; 01569 } 01570 } 01571 // File versioning... 01572 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) { 01573 $this->mImageTimeKeys[$dbk] = $data; 01574 } 01575 01576 // Hooks registered in the object 01577 global $wgParserOutputHooks; 01578 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) { 01579 list( $hookName, $data ) = $hookInfo; 01580 if ( isset( $wgParserOutputHooks[$hookName] ) ) { 01581 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data ); 01582 } 01583 } 01584 01585 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); 01586 } 01587 01593 function addParserOutput( &$parserOutput ) { 01594 $this->addParserOutputNoText( $parserOutput ); 01595 $text = $parserOutput->getText(); 01596 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) ); 01597 $this->addHTML( $text ); 01598 } 01599 01605 public function addTemplate( &$template ) { 01606 ob_start(); 01607 $template->execute(); 01608 $this->addHTML( ob_get_contents() ); 01609 ob_end_clean(); 01610 } 01611 01626 public function parse( $text, $linestart = true, $interface = false, $language = null ) { 01627 global $wgParser; 01628 01629 if( is_null( $this->getTitle() ) ) { 01630 throw new MWException( 'Empty $mTitle in ' . __METHOD__ ); 01631 } 01632 01633 $popts = $this->parserOptions(); 01634 if ( $interface ) { 01635 $popts->setInterfaceMessage( true ); 01636 } 01637 if ( $language !== null ) { 01638 $oldLang = $popts->setTargetLanguage( $language ); 01639 } 01640 01641 $parserOutput = $wgParser->parse( 01642 $text, $this->getTitle(), $popts, 01643 $linestart, true, $this->mRevisionId 01644 ); 01645 01646 if ( $interface ) { 01647 $popts->setInterfaceMessage( false ); 01648 } 01649 if ( $language !== null ) { 01650 $popts->setTargetLanguage( $oldLang ); 01651 } 01652 01653 return $parserOutput->getText(); 01654 } 01655 01666 public function parseInline( $text, $linestart = true, $interface = false ) { 01667 $parsed = $this->parse( $text, $linestart, $interface ); 01668 01669 $m = array(); 01670 if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) { 01671 $parsed = $m[1]; 01672 } 01673 01674 return $parsed; 01675 } 01676 01682 public function setSquidMaxage( $maxage ) { 01683 $this->mSquidMaxage = $maxage; 01684 } 01685 01693 public function enableClientCache( $state ) { 01694 return wfSetVar( $this->mEnableClientCache, $state ); 01695 } 01696 01702 function getCacheVaryCookies() { 01703 global $wgCookiePrefix, $wgCacheVaryCookies; 01704 static $cookies; 01705 if ( $cookies === null ) { 01706 $cookies = array_merge( 01707 array( 01708 "{$wgCookiePrefix}Token", 01709 "{$wgCookiePrefix}LoggedOut", 01710 session_name() 01711 ), 01712 $wgCacheVaryCookies 01713 ); 01714 wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) ); 01715 } 01716 return $cookies; 01717 } 01718 01725 function haveCacheVaryCookies() { 01726 $cookieHeader = $this->getRequest()->getHeader( 'cookie' ); 01727 if ( $cookieHeader === false ) { 01728 return false; 01729 } 01730 $cvCookies = $this->getCacheVaryCookies(); 01731 foreach ( $cvCookies as $cookieName ) { 01732 # Check for a simple string match, like the way squid does it 01733 if ( strpos( $cookieHeader, $cookieName ) !== false ) { 01734 wfDebug( __METHOD__ . ": found $cookieName\n" ); 01735 return true; 01736 } 01737 } 01738 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" ); 01739 return false; 01740 } 01741 01750 public function addVaryHeader( $header, $option = null ) { 01751 if ( !array_key_exists( $header, $this->mVaryHeader ) ) { 01752 $this->mVaryHeader[$header] = (array)$option; 01753 } elseif( is_array( $option ) ) { 01754 if( is_array( $this->mVaryHeader[$header] ) ) { 01755 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option ); 01756 } else { 01757 $this->mVaryHeader[$header] = $option; 01758 } 01759 } 01760 $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] ); 01761 } 01762 01769 public function getVaryHeader() { 01770 return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ); 01771 } 01772 01778 public function getXVO() { 01779 $cvCookies = $this->getCacheVaryCookies(); 01780 01781 $cookiesOption = array(); 01782 foreach ( $cvCookies as $cookieName ) { 01783 $cookiesOption[] = 'string-contains=' . $cookieName; 01784 } 01785 $this->addVaryHeader( 'Cookie', $cookiesOption ); 01786 01787 $headers = array(); 01788 foreach( $this->mVaryHeader as $header => $option ) { 01789 $newheader = $header; 01790 if ( is_array( $option ) && count( $option ) > 0 ) { 01791 $newheader .= ';' . implode( ';', $option ); 01792 } 01793 $headers[] = $newheader; 01794 } 01795 $xvo = 'X-Vary-Options: ' . implode( ',', $headers ); 01796 01797 return $xvo; 01798 } 01799 01808 function addAcceptLanguage() { 01809 $lang = $this->getTitle()->getPageLanguage(); 01810 if( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) { 01811 $variants = $lang->getVariants(); 01812 $aloption = array(); 01813 foreach ( $variants as $variant ) { 01814 if( $variant === $lang->getCode() ) { 01815 continue; 01816 } else { 01817 $aloption[] = 'string-contains=' . $variant; 01818 01819 // IE and some other browsers use BCP 47 standards in 01820 // their Accept-Language header, like "zh-CN" or "zh-Hant". 01821 // We should handle these too. 01822 $variantBCP47 = wfBCP47( $variant ); 01823 if ( $variantBCP47 !== $variant ) { 01824 $aloption[] = 'string-contains=' . $variantBCP47; 01825 } 01826 } 01827 } 01828 $this->addVaryHeader( 'Accept-Language', $aloption ); 01829 } 01830 } 01831 01842 public function preventClickjacking( $enable = true ) { 01843 $this->mPreventClickjacking = $enable; 01844 } 01845 01851 public function allowClickjacking() { 01852 $this->mPreventClickjacking = false; 01853 } 01854 01862 public function getFrameOptions() { 01863 global $wgBreakFrames, $wgEditPageFrameOptions; 01864 if ( $wgBreakFrames ) { 01865 return 'DENY'; 01866 } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) { 01867 return $wgEditPageFrameOptions; 01868 } 01869 return false; 01870 } 01871 01875 public function sendCacheControl() { 01876 global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO; 01877 01878 $response = $this->getRequest()->response(); 01879 if ( $wgUseETag && $this->mETag ) { 01880 $response->header( "ETag: $this->mETag" ); 01881 } 01882 01883 $this->addVaryHeader( 'Cookie' ); 01884 $this->addAcceptLanguage(); 01885 01886 # don't serve compressed data to clients who can't handle it 01887 # maintain different caches for logged-in users and non-logged in ones 01888 $response->header( $this->getVaryHeader() ); 01889 01890 if ( $wgUseXVO ) { 01891 # Add an X-Vary-Options header for Squid with Wikimedia patches 01892 $response->header( $this->getXVO() ); 01893 } 01894 01895 if( $this->mEnableClientCache ) { 01896 if( 01897 $wgUseSquid && session_id() == '' && !$this->isPrintable() && 01898 $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() 01899 ) 01900 { 01901 if ( $wgUseESI ) { 01902 # We'll purge the proxy cache explicitly, but require end user agents 01903 # to revalidate against the proxy on each visit. 01904 # Surrogate-Control controls our Squid, Cache-Control downstream caches 01905 wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false ); 01906 # start with a shorter timeout for initial testing 01907 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"'); 01908 $response->header( 'Surrogate-Control: max-age=' . $wgSquidMaxage . '+' . $this->mSquidMaxage . ', content="ESI/1.0"' ); 01909 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); 01910 } else { 01911 # We'll purge the proxy cache for anons explicitly, but require end user agents 01912 # to revalidate against the proxy on each visit. 01913 # IMPORTANT! The Squid needs to replace the Cache-Control header with 01914 # Cache-Control: s-maxage=0, must-revalidate, max-age=0 01915 wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false ); 01916 # start with a shorter timeout for initial testing 01917 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" ); 01918 $response->header( 'Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0' ); 01919 } 01920 } else { 01921 # We do want clients to cache if they can, but they *must* check for updates 01922 # on revisiting the page. 01923 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false ); 01924 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01925 $response->header( "Cache-Control: private, must-revalidate, max-age=0" ); 01926 } 01927 if( $this->mLastModified ) { 01928 $response->header( "Last-Modified: {$this->mLastModified}" ); 01929 } 01930 } else { 01931 wfDebug( __METHOD__ . ": no caching **\n", false ); 01932 01933 # In general, the absence of a last modified header should be enough to prevent 01934 # the client from using its cache. We send a few other things just to make sure. 01935 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01936 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); 01937 $response->header( 'Pragma: no-cache' ); 01938 } 01939 } 01940 01950 public static function getStatusMessage( $code ) { 01951 wfDeprecated( __METHOD__, '1.18' ); 01952 return HttpStatus::getMessage( $code ); 01953 } 01954 01959 public function output() { 01960 global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP; 01961 01962 if( $this->mDoNothing ) { 01963 return; 01964 } 01965 01966 wfProfileIn( __METHOD__ ); 01967 01968 $response = $this->getRequest()->response(); 01969 01970 if ( $this->mRedirect != '' ) { 01971 # Standards require redirect URLs to be absolute 01972 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT ); 01973 01974 $redirect = $this->mRedirect; 01975 $code = $this->mRedirectCode; 01976 01977 if( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) { 01978 if( $code == '301' || $code == '303' ) { 01979 if( !$wgDebugRedirects ) { 01980 $message = HttpStatus::getMessage( $code ); 01981 $response->header( "HTTP/1.1 $code $message" ); 01982 } 01983 $this->mLastModified = wfTimestamp( TS_RFC2822 ); 01984 } 01985 if ( $wgVaryOnXFP ) { 01986 $this->addVaryHeader( 'X-Forwarded-Proto' ); 01987 } 01988 $this->sendCacheControl(); 01989 01990 $response->header( "Content-Type: text/html; charset=utf-8" ); 01991 if( $wgDebugRedirects ) { 01992 $url = htmlspecialchars( $redirect ); 01993 print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n"; 01994 print "<p>Location: <a href=\"$url\">$url</a></p>\n"; 01995 print "</body>\n</html>\n"; 01996 } else { 01997 $response->header( 'Location: ' . $redirect ); 01998 } 01999 } 02000 02001 wfProfileOut( __METHOD__ ); 02002 return; 02003 } elseif ( $this->mStatusCode ) { 02004 $message = HttpStatus::getMessage( $this->mStatusCode ); 02005 if ( $message ) { 02006 $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message ); 02007 } 02008 } 02009 02010 # Buffer output; final headers may depend on later processing 02011 ob_start(); 02012 02013 $response->header( "Content-type: $wgMimeType; charset=UTF-8" ); 02014 $response->header( 'Content-language: ' . $wgLanguageCode ); 02015 02016 // Prevent framing, if requested 02017 $frameOptions = $this->getFrameOptions(); 02018 if ( $frameOptions ) { 02019 $response->header( "X-Frame-Options: $frameOptions" ); 02020 } 02021 02022 if ( $this->mArticleBodyOnly ) { 02023 $this->out( $this->mBodytext ); 02024 } else { 02025 $this->addDefaultModules(); 02026 02027 $sk = $this->getSkin(); 02028 02029 // Hook that allows last minute changes to the output page, e.g. 02030 // adding of CSS or Javascript by extensions. 02031 wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) ); 02032 02033 wfProfileIn( 'Output-skin' ); 02034 $sk->outputPage(); 02035 wfProfileOut( 'Output-skin' ); 02036 } 02037 02038 // This hook allows last minute changes to final overall output by modifying output buffer 02039 wfRunHooks( 'AfterFinalPageOutput', array( $this ) ); 02040 02041 $this->sendCacheControl(); 02042 02043 ob_end_flush(); 02044 02045 wfProfileOut( __METHOD__ ); 02046 } 02047 02053 public function out( $ins ) { 02054 print $ins; 02055 } 02056 02061 function blockedPage() { 02062 throw new UserBlockedError( $this->getUser()->mBlock ); 02063 } 02064 02075 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) { 02076 $this->setPageTitle( $pageTitle ); 02077 if ( $htmlTitle !== false ) { 02078 $this->setHTMLTitle( $htmlTitle ); 02079 } 02080 $this->setRobotPolicy( 'noindex,nofollow' ); 02081 $this->setArticleRelated( false ); 02082 $this->enableClientCache( false ); 02083 $this->mRedirect = ''; 02084 $this->clearSubtitle(); 02085 $this->clearHTML(); 02086 } 02087 02099 public function showErrorPage( $title, $msg, $params = array() ) { 02100 if( !$title instanceof Message ) { 02101 $title = $this->msg( $title ); 02102 } 02103 02104 $this->prepareErrorPage( $title ); 02105 02106 if ( $msg instanceof Message ) { 02107 $this->addHTML( $msg->parseAsBlock() ); 02108 } else { 02109 $this->addWikiMsgArray( $msg, $params ); 02110 } 02111 02112 $this->returnToMain(); 02113 } 02114 02121 public function showPermissionsErrorPage( $errors, $action = null ) { 02122 // For some action (read, edit, create and upload), display a "login to do this action" 02123 // error if all of the following conditions are met: 02124 // 1. the user is not logged in 02125 // 2. the only error is insufficient permissions (i.e. no block or something else) 02126 // 3. the error can be avoided simply by logging in 02127 if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) ) 02128 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] ) 02129 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' ) 02130 && ( User::groupHasPermission( 'user', $action ) 02131 || User::groupHasPermission( 'autoconfirmed', $action ) ) 02132 ) { 02133 $displayReturnto = null; 02134 02135 # Due to bug 32276, if a user does not have read permissions, 02136 # $this->getTitle() will just give Special:Badtitle, which is 02137 # not especially useful as a returnto parameter. Use the title 02138 # from the request instead, if there was one. 02139 $request = $this->getRequest(); 02140 $returnto = Title::newFromURL( $request->getVal( 'title', '' ) ); 02141 if ( $action == 'edit' ) { 02142 $msg = 'whitelistedittext'; 02143 $displayReturnto = $returnto; 02144 } elseif ( $action == 'createpage' || $action == 'createtalk' ) { 02145 $msg = 'nocreatetext'; 02146 } elseif ( $action == 'upload' ) { 02147 $msg = 'uploadnologintext'; 02148 } else { # Read 02149 $msg = 'loginreqpagetext'; 02150 $displayReturnto = Title::newMainPage(); 02151 } 02152 02153 $query = array(); 02154 02155 if ( $returnto ) { 02156 $query['returnto'] = $returnto->getPrefixedText(); 02157 02158 if ( !$request->wasPosted() ) { 02159 $returntoquery = $request->getValues(); 02160 unset( $returntoquery['title'] ); 02161 unset( $returntoquery['returnto'] ); 02162 unset( $returntoquery['returntoquery'] ); 02163 $query['returntoquery'] = wfArrayToCgi( $returntoquery ); 02164 } 02165 } 02166 $loginLink = Linker::linkKnown( 02167 SpecialPage::getTitleFor( 'Userlogin' ), 02168 $this->msg( 'loginreqlink' )->escaped(), 02169 array(), 02170 $query 02171 ); 02172 02173 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) ); 02174 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() ); 02175 02176 # Don't return to a page the user can't read otherwise 02177 # we'll end up in a pointless loop 02178 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) { 02179 $this->returnToMain( null, $displayReturnto ); 02180 } 02181 } else { 02182 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) ); 02183 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) ); 02184 } 02185 } 02186 02193 public function versionRequired( $version ) { 02194 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) ); 02195 02196 $this->addWikiMsg( 'versionrequiredtext', $version ); 02197 $this->returnToMain(); 02198 } 02199 02206 public function permissionRequired( $permission ) { 02207 throw new PermissionsError( $permission ); 02208 } 02209 02215 public function loginToUse() { 02216 throw new PermissionsError( 'read' ); 02217 } 02218 02226 public function formatPermissionsErrorMessage( $errors, $action = null ) { 02227 if ( $action == null ) { 02228 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n"; 02229 } else { 02230 $action_desc = $this->msg( "action-$action" )->plain(); 02231 $text = $this->msg( 02232 'permissionserrorstext-withaction', 02233 count( $errors ), 02234 $action_desc 02235 )->plain() . "\n\n"; 02236 } 02237 02238 if ( count( $errors ) > 1 ) { 02239 $text .= '<ul class="permissions-errors">' . "\n"; 02240 02241 foreach( $errors as $error ) { 02242 $text .= '<li>'; 02243 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain(); 02244 $text .= "</li>\n"; 02245 } 02246 $text .= '</ul>'; 02247 } else { 02248 $text .= "<div class=\"permissions-errors\">\n" . 02249 call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() . 02250 "\n</div>"; 02251 } 02252 02253 return $text; 02254 } 02255 02277 public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) { 02278 $this->setRobotPolicy( 'noindex,nofollow' ); 02279 $this->setArticleRelated( false ); 02280 02281 // If no reason is given, just supply a default "I can't let you do 02282 // that, Dave" message. Should only occur if called by legacy code. 02283 if ( $protected && empty( $reasons ) ) { 02284 $reasons[] = array( 'badaccess-group0' ); 02285 } 02286 02287 if ( !empty( $reasons ) ) { 02288 // Permissions error 02289 if( $source ) { 02290 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) ); 02291 $this->addBacklinkSubtitle( $this->getTitle() ); 02292 } else { 02293 $this->setPageTitle( $this->msg( 'badaccess' ) ); 02294 } 02295 $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) ); 02296 } else { 02297 // Wiki is read only 02298 throw new ReadOnlyError; 02299 } 02300 02301 // Show source, if supplied 02302 if( is_string( $source ) ) { 02303 $this->addWikiMsg( 'viewsourcetext' ); 02304 02305 $pageLang = $this->getTitle()->getPageLanguage(); 02306 $params = array( 02307 'id' => 'wpTextbox1', 02308 'name' => 'wpTextbox1', 02309 'cols' => $this->getUser()->getOption( 'cols' ), 02310 'rows' => $this->getUser()->getOption( 'rows' ), 02311 'readonly' => 'readonly', 02312 'lang' => $pageLang->getHtmlCode(), 02313 'dir' => $pageLang->getDir(), 02314 ); 02315 $this->addHTML( Html::element( 'textarea', $params, $source ) ); 02316 02317 // Show templates used by this article 02318 $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() ); 02319 $this->addHTML( "<div class='templatesUsed'> 02320 $templates 02321 </div> 02322 " ); 02323 } 02324 02325 # If the title doesn't exist, it's fairly pointless to print a return 02326 # link to it. After all, you just tried editing it and couldn't, so 02327 # what's there to do there? 02328 if( $this->getTitle()->exists() ) { 02329 $this->returnToMain( null, $this->getTitle() ); 02330 } 02331 } 02332 02337 public function rateLimited() { 02338 throw new ThrottledError; 02339 } 02340 02350 public function showLagWarning( $lag ) { 02351 global $wgSlaveLagWarning, $wgSlaveLagCritical; 02352 if( $lag >= $wgSlaveLagWarning ) { 02353 $message = $lag < $wgSlaveLagCritical 02354 ? 'lag-warn-normal' 02355 : 'lag-warn-high'; 02356 $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" ); 02357 $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) ); 02358 } 02359 } 02360 02361 public function showFatalError( $message ) { 02362 $this->prepareErrorPage( $this->msg( 'internalerror' ) ); 02363 02364 $this->addHTML( $message ); 02365 } 02366 02367 public function showUnexpectedValueError( $name, $val ) { 02368 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() ); 02369 } 02370 02371 public function showFileCopyError( $old, $new ) { 02372 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() ); 02373 } 02374 02375 public function showFileRenameError( $old, $new ) { 02376 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() ); 02377 } 02378 02379 public function showFileDeleteError( $name ) { 02380 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() ); 02381 } 02382 02383 public function showFileNotFoundError( $name ) { 02384 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() ); 02385 } 02386 02395 public function addReturnTo( $title, $query = array(), $text = null, $options = array() ) { 02396 if( in_array( 'http', $options ) ) { 02397 $proto = PROTO_HTTP; 02398 } elseif( in_array( 'https', $options ) ) { 02399 $proto = PROTO_HTTPS; 02400 } else { 02401 $proto = PROTO_RELATIVE; 02402 } 02403 02404 $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL( '', false, $proto ) ) ); 02405 $link = $this->msg( 'returnto' )->rawParams( 02406 Linker::link( $title, $text, array(), $query, $options ) )->escaped(); 02407 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" ); 02408 } 02409 02418 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) { 02419 if ( $returnto == null ) { 02420 $returnto = $this->getRequest()->getText( 'returnto' ); 02421 } 02422 02423 if ( $returntoquery == null ) { 02424 $returntoquery = $this->getRequest()->getText( 'returntoquery' ); 02425 } 02426 02427 if ( $returnto === '' ) { 02428 $returnto = Title::newMainPage(); 02429 } 02430 02431 if ( is_object( $returnto ) ) { 02432 $titleObj = $returnto; 02433 } else { 02434 $titleObj = Title::newFromText( $returnto ); 02435 } 02436 if ( !is_object( $titleObj ) ) { 02437 $titleObj = Title::newMainPage(); 02438 } 02439 02440 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) ); 02441 } 02442 02448 public function headElement( Skin $sk, $includeStyle = true ) { 02449 global $wgContLang; 02450 02451 $userdir = $this->getLanguage()->getDir(); 02452 $sitedir = $wgContLang->getDir(); 02453 02454 if ( $sk->commonPrintStylesheet() ) { 02455 $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' ); 02456 } 02457 02458 $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) ); 02459 02460 if ( $this->getHTMLTitle() == '' ) { 02461 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) ); 02462 } 02463 02464 $openHead = Html::openElement( 'head' ); 02465 if ( $openHead ) { 02466 # Don't bother with the newline if $head == '' 02467 $ret .= "$openHead\n"; 02468 } 02469 02470 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n"; 02471 02472 $ret .= implode( "\n", array( 02473 $this->getHeadLinks( null, true ), 02474 $this->buildCssLinks(), 02475 $this->getHeadScripts(), 02476 $this->getHeadItems() 02477 ) ); 02478 02479 $closeHead = Html::closeElement( 'head' ); 02480 if ( $closeHead ) { 02481 $ret .= "$closeHead\n"; 02482 } 02483 02484 $bodyAttrs = array(); 02485 02486 # Classes for LTR/RTL directionality support 02487 $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir"; 02488 02489 if ( $this->getLanguage()->capitalizeAllNouns() ) { 02490 # A <body> class is probably not the best way to do this . . . 02491 $bodyAttrs['class'] .= ' capitalize-all-nouns'; 02492 } 02493 $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() ); 02494 $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() ); 02495 $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) ); 02496 02497 $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need 02498 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) ); 02499 02500 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n"; 02501 02502 return $ret; 02503 } 02504 02508 private function addDefaultModules() { 02509 global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax, 02510 $wgAjaxWatch, $wgResponsiveImages; 02511 02512 // Add base resources 02513 $this->addModules( array( 02514 'mediawiki.user', 02515 'mediawiki.page.startup', 02516 'mediawiki.page.ready', 02517 ) ); 02518 if ( $wgIncludeLegacyJavaScript ) { 02519 $this->addModules( 'mediawiki.legacy.wikibits' ); 02520 } 02521 02522 if ( $wgPreloadJavaScriptMwUtil ) { 02523 $this->addModules( 'mediawiki.util' ); 02524 } 02525 02526 MWDebug::addModules( $this ); 02527 02528 // Add various resources if required 02529 if ( $wgUseAjax ) { 02530 $this->addModules( 'mediawiki.legacy.ajax' ); 02531 02532 wfRunHooks( 'AjaxAddScript', array( &$this ) ); 02533 02534 if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) { 02535 $this->addModules( 'mediawiki.page.watch.ajax' ); 02536 } 02537 02538 if ( !$this->getUser()->getOption( 'disablesuggest', false ) ) { 02539 $this->addModules( 'mediawiki.searchSuggest' ); 02540 } 02541 } 02542 02543 if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) { 02544 $this->addModules( 'mediawiki.action.view.rightClickEdit' ); 02545 } 02546 02547 # Crazy edit-on-double-click stuff 02548 if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) { 02549 $this->addModules( 'mediawiki.action.view.dblClickEdit' ); 02550 } 02551 02552 // Support for high-density display images 02553 if ( $wgResponsiveImages ) { 02554 $this->addModules( 'mediawiki.hidpi' ); 02555 } 02556 } 02557 02563 public function getResourceLoader() { 02564 if ( is_null( $this->mResourceLoader ) ) { 02565 $this->mResourceLoader = new ResourceLoader(); 02566 } 02567 return $this->mResourceLoader; 02568 } 02569 02579 protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) { 02580 global $wgResourceLoaderUseESI; 02581 02582 $modules = (array) $modules; 02583 02584 if ( !count( $modules ) ) { 02585 return ''; 02586 } 02587 02588 if ( count( $modules ) > 1 ) { 02589 // Remove duplicate module requests 02590 $modules = array_unique( $modules ); 02591 // Sort module names so requests are more uniform 02592 sort( $modules ); 02593 02594 if ( ResourceLoader::inDebugMode() ) { 02595 // Recursively call us for every item 02596 $links = ''; 02597 foreach ( $modules as $name ) { 02598 $links .= $this->makeResourceLoaderLink( $name, $only, $useESI ); 02599 } 02600 return $links; 02601 } 02602 } 02603 02604 // Create keyed-by-group list of module objects from modules list 02605 $groups = array(); 02606 $resourceLoader = $this->getResourceLoader(); 02607 foreach ( $modules as $name ) { 02608 $module = $resourceLoader->getModule( $name ); 02609 # Check that we're allowed to include this module on this page 02610 if ( !$module 02611 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) 02612 && $only == ResourceLoaderModule::TYPE_SCRIPTS ) 02613 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES ) 02614 && $only == ResourceLoaderModule::TYPE_STYLES ) 02615 ) 02616 { 02617 continue; 02618 } 02619 02620 $group = $module->getGroup(); 02621 if ( !isset( $groups[$group] ) ) { 02622 $groups[$group] = array(); 02623 } 02624 $groups[$group][$name] = $module; 02625 } 02626 02627 $links = ''; 02628 foreach ( $groups as $group => $grpModules ) { 02629 // Special handling for user-specific groups 02630 $user = null; 02631 if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) { 02632 $user = $this->getUser()->getName(); 02633 } 02634 02635 // Create a fake request based on the one we are about to make so modules return 02636 // correct timestamp and emptiness data 02637 $query = ResourceLoader::makeLoaderQuery( 02638 array(), // modules; not determined yet 02639 $this->getLanguage()->getCode(), 02640 $this->getSkin()->getSkinName(), 02641 $user, 02642 null, // version; not determined yet 02643 ResourceLoader::inDebugMode(), 02644 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02645 $this->isPrintable(), 02646 $this->getRequest()->getBool( 'handheld' ), 02647 $extraQuery 02648 ); 02649 $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); 02650 // Extract modules that know they're empty 02651 $emptyModules = array (); 02652 foreach ( $grpModules as $key => $module ) { 02653 if ( $module->isKnownEmpty( $context ) ) { 02654 $emptyModules[$key] = 'ready'; 02655 unset( $grpModules[$key] ); 02656 } 02657 } 02658 // Inline empty modules: since they're empty, just mark them as 'ready' 02659 if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) { 02660 // If we're only getting the styles, we don't need to do anything for empty modules. 02661 $links .= Html::inlineScript( 02662 02663 ResourceLoader::makeLoaderConditionalScript( 02664 02665 ResourceLoader::makeLoaderStateScript( $emptyModules ) 02666 02667 ) 02668 02669 ) . "\n"; 02670 } 02671 02672 // If there are no modules left, skip this group 02673 if ( count( $grpModules ) === 0 ) { 02674 continue; 02675 } 02676 02677 // Inline private modules. These can't be loaded through load.php for security 02678 // reasons, see bug 34907. Note that these modules should be loaded from 02679 // getHeadScripts() before the first loader call. Otherwise other modules can't 02680 // properly use them as dependencies (bug 30914) 02681 if ( $group === 'private' ) { 02682 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02683 $links .= Html::inlineStyle( 02684 $resourceLoader->makeModuleResponse( $context, $grpModules ) 02685 ); 02686 } else { 02687 $links .= Html::inlineScript( 02688 ResourceLoader::makeLoaderConditionalScript( 02689 $resourceLoader->makeModuleResponse( $context, $grpModules ) 02690 ) 02691 ); 02692 } 02693 $links .= "\n"; 02694 continue; 02695 } 02696 // Special handling for the user group; because users might change their stuff 02697 // on-wiki like user pages, or user preferences; we need to find the highest 02698 // timestamp of these user-changeable modules so we can ensure cache misses on change 02699 // This should NOT be done for the site group (bug 27564) because anons get that too 02700 // and we shouldn't be putting timestamps in Squid-cached HTML 02701 $version = null; 02702 if ( $group === 'user' ) { 02703 // Get the maximum timestamp 02704 $timestamp = 1; 02705 foreach ( $grpModules as $module ) { 02706 $timestamp = max( $timestamp, $module->getModifiedTime( $context ) ); 02707 } 02708 // Add a version parameter so cache will break when things change 02709 $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp ); 02710 } 02711 02712 $url = ResourceLoader::makeLoaderURL( 02713 array_keys( $grpModules ), 02714 $this->getLanguage()->getCode(), 02715 $this->getSkin()->getSkinName(), 02716 $user, 02717 $version, 02718 ResourceLoader::inDebugMode(), 02719 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02720 $this->isPrintable(), 02721 $this->getRequest()->getBool( 'handheld' ), 02722 $extraQuery 02723 ); 02724 if ( $useESI && $wgResourceLoaderUseESI ) { 02725 $esi = Xml::element( 'esi:include', array( 'src' => $url ) ); 02726 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02727 $link = Html::inlineStyle( $esi ); 02728 } else { 02729 $link = Html::inlineScript( $esi ); 02730 } 02731 } else { 02732 // Automatically select style/script elements 02733 if ( $only === ResourceLoaderModule::TYPE_STYLES ) { 02734 $link = Html::linkedStyle( $url ); 02735 } else if ( $loadCall ) { 02736 $link = Html::inlineScript( 02737 ResourceLoader::makeLoaderConditionalScript( 02738 Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) ) 02739 ) 02740 ); 02741 } else { 02742 $link = Html::linkedScript( $url ); 02743 } 02744 } 02745 02746 if( $group == 'noscript' ) { 02747 $links .= Html::rawElement( 'noscript', array(), $link ) . "\n"; 02748 } else { 02749 $links .= $link . "\n"; 02750 } 02751 } 02752 return $links; 02753 } 02754 02761 function getHeadScripts() { 02762 global $wgResourceLoaderExperimentalAsyncLoading; 02763 02764 // Startup - this will immediately load jquery and mediawiki modules 02765 $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true ); 02766 02767 // Load config before anything else 02768 $scripts .= Html::inlineScript( 02769 ResourceLoader::makeLoaderConditionalScript( 02770 ResourceLoader::makeConfigSetScript( $this->getJSVars() ) 02771 ) 02772 ); 02773 02774 // Load embeddable private modules before any loader links 02775 // This needs to be TYPE_COMBINED so these modules are properly wrapped 02776 // in mw.loader.implement() calls and deferred until mw.user is available 02777 $embedScripts = array( 'user.options', 'user.tokens' ); 02778 $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED ); 02779 02780 // Script and Messages "only" requests marked for top inclusion 02781 // Messages should go first 02782 $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES ); 02783 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS ); 02784 02785 // Modules requests - let the client calculate dependencies and batch requests as it likes 02786 // Only load modules that have marked themselves for loading at the top 02787 $modules = $this->getModules( true, 'top' ); 02788 if ( $modules ) { 02789 $scripts .= Html::inlineScript( 02790 ResourceLoader::makeLoaderConditionalScript( 02791 Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) 02792 ) 02793 ); 02794 } 02795 02796 if ( $wgResourceLoaderExperimentalAsyncLoading ) { 02797 $scripts .= $this->getScriptsForBottomQueue( true ); 02798 } 02799 02800 return $scripts; 02801 } 02802 02812 function getScriptsForBottomQueue( $inHead ) { 02813 global $wgUseSiteJs, $wgAllowUserJs; 02814 02815 // Script and Messages "only" requests marked for bottom inclusion 02816 // If we're in the <head>, use load() calls rather than <script src="..."> tags 02817 // Messages should go first 02818 $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), 02819 ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(), 02820 /* $loadCall = */ $inHead 02821 ); 02822 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), 02823 ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(), 02824 /* $loadCall = */ $inHead 02825 ); 02826 02827 // Modules requests - let the client calculate dependencies and batch requests as it likes 02828 // Only load modules that have marked themselves for loading at the bottom 02829 $modules = $this->getModules( true, 'bottom' ); 02830 if ( $modules ) { 02831 $scripts .= Html::inlineScript( 02832 ResourceLoader::makeLoaderConditionalScript( 02833 Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) ) 02834 ) 02835 ); 02836 } 02837 02838 // Legacy Scripts 02839 $scripts .= "\n" . $this->mScripts; 02840 02841 $defaultModules = array(); 02842 02843 // Add site JS if enabled 02844 if ( $wgUseSiteJs ) { 02845 $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS, 02846 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02847 ); 02848 $defaultModules['site'] = 'loading'; 02849 } else { 02850 // The wiki is configured to not allow a site module. 02851 $defaultModules['site'] = 'missing'; 02852 } 02853 02854 // Add user JS if enabled 02855 if ( $wgAllowUserJs ) { 02856 if ( $this->getUser()->isLoggedIn() ) { 02857 if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) { 02858 # XXX: additional security check/prompt? 02859 // We're on a preview of a JS subpage 02860 // Exclude this page from the user module in case it's in there (bug 26283) 02861 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false, 02862 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead 02863 ); 02864 // Load the previewed JS 02865 $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n"; 02866 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded 02867 // asynchronously and may arrive *after* the inline script here. So the previewed code 02868 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js... 02869 } else { 02870 // Include the user module normally, i.e., raw to avoid it being wrapped in a closure. 02871 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, 02872 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02873 ); 02874 } 02875 $defaultModules['user'] = 'loading'; 02876 } else { 02877 // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid 02878 // blocking default gadgets that might depend on it. Although arguably default-enabled 02879 // gadgets should not depend on the user module, it's harmless and less error-prone to 02880 // handle this case. 02881 $defaultModules['user'] = 'ready'; 02882 } 02883 } else { 02884 // User JS disabled 02885 $defaultModules['user'] = 'missing'; 02886 } 02887 02888 // Group JS is only enabled if site JS is enabled. 02889 if ( $wgUseSiteJs ) { 02890 if ( $this->getUser()->isLoggedIn() ) { 02891 $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED, 02892 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 02893 ); 02894 $defaultModules['user.groups'] = 'loading'; 02895 } else { 02896 // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to 02897 // avoid blocking gadgets that might depend upon the module. 02898 $defaultModules['user.groups'] = 'ready'; 02899 } 02900 } else { 02901 // Site (and group JS) disabled 02902 $defaultModules['user.groups'] = 'missing'; 02903 } 02904 02905 $loaderInit = ''; 02906 if ( $inHead ) { 02907 // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'. 02908 foreach ( $defaultModules as $m => $state ) { 02909 if ( $state == 'loading' ) { 02910 unset( $defaultModules[$m] ); 02911 } 02912 } 02913 } 02914 if ( count( $defaultModules ) > 0 ) { 02915 $loaderInit = Html::inlineScript( 02916 ResourceLoader::makeLoaderConditionalScript( 02917 ResourceLoader::makeLoaderStateScript( $defaultModules ) 02918 ) 02919 ) . "\n"; 02920 } 02921 return $loaderInit . $scripts; 02922 } 02923 02928 function getBottomScripts() { 02929 global $wgResourceLoaderExperimentalAsyncLoading; 02930 if ( !$wgResourceLoaderExperimentalAsyncLoading ) { 02931 return $this->getScriptsForBottomQueue( false ); 02932 } else { 02933 return ''; 02934 } 02935 } 02936 02943 public function addJsConfigVars( $keys, $value = null ) { 02944 if ( is_array( $keys ) ) { 02945 foreach ( $keys as $key => $value ) { 02946 $this->mJsConfigVars[$key] = $value; 02947 } 02948 return; 02949 } 02950 02951 $this->mJsConfigVars[$keys] = $value; 02952 } 02953 02966 public function getJSVars() { 02967 global $wgContLang; 02968 02969 $latestRevID = 0; 02970 $pageID = 0; 02971 $canonicalName = false; # bug 21115 02972 02973 $title = $this->getTitle(); 02974 $ns = $title->getNamespace(); 02975 $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText(); 02976 02977 // Get the relevant title so that AJAX features can use the correct page name 02978 // when making API requests from certain special pages (bug 34972). 02979 $relevantTitle = $this->getSkin()->getRelevantTitle(); 02980 02981 if ( $ns == NS_SPECIAL ) { 02982 list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); 02983 } elseif ( $this->canUseWikiPage() ) { 02984 $wikiPage = $this->getWikiPage(); 02985 $latestRevID = $wikiPage->getLatest(); 02986 $pageID = $wikiPage->getId(); 02987 } 02988 02989 $lang = $title->getPageLanguage(); 02990 02991 // Pre-process information 02992 $separatorTransTable = $lang->separatorTransformTable(); 02993 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); 02994 $compactSeparatorTransTable = array( 02995 implode( "\t", array_keys( $separatorTransTable ) ), 02996 implode( "\t", $separatorTransTable ), 02997 ); 02998 $digitTransTable = $lang->digitTransformTable(); 02999 $digitTransTable = $digitTransTable ? $digitTransTable : array(); 03000 $compactDigitTransTable = array( 03001 implode( "\t", array_keys( $digitTransTable ) ), 03002 implode( "\t", $digitTransTable ), 03003 ); 03004 03005 $user = $this->getUser(); 03006 03007 $vars = array( 03008 'wgCanonicalNamespace' => $nsname, 03009 'wgCanonicalSpecialPageName' => $canonicalName, 03010 'wgNamespaceNumber' => $title->getNamespace(), 03011 'wgPageName' => $title->getPrefixedDBkey(), 03012 'wgTitle' => $title->getText(), 03013 'wgCurRevisionId' => $latestRevID, 03014 'wgArticleId' => $pageID, 03015 'wgIsArticle' => $this->isArticle(), 03016 'wgAction' => Action::getActionName( $this->getContext() ), 03017 'wgUserName' => $user->isAnon() ? null : $user->getName(), 03018 'wgUserGroups' => $user->getEffectiveGroups(), 03019 'wgCategories' => $this->getCategories(), 03020 'wgBreakFrames' => $this->getFrameOptions() == 'DENY', 03021 'wgPageContentLanguage' => $lang->getCode(), 03022 'wgSeparatorTransformTable' => $compactSeparatorTransTable, 03023 'wgDigitTransformTable' => $compactDigitTransTable, 03024 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(), 03025 'wgMonthNames' => $lang->getMonthNamesArray(), 03026 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(), 03027 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(), 03028 ); 03029 if ( $user->isLoggedIn() ) { 03030 $vars['wgUserId'] = $user->getId(); 03031 $vars['wgUserEditCount'] = $user->getEditCount(); 03032 $userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() ); 03033 $vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null; 03034 } 03035 if ( $wgContLang->hasVariants() ) { 03036 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); 03037 } 03038 foreach ( $title->getRestrictionTypes() as $type ) { 03039 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type ); 03040 } 03041 if ( $title->isMainPage() ) { 03042 $vars['wgIsMainPage'] = true; 03043 } 03044 if ( $this->mRedirectedFrom ) { 03045 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey(); 03046 } 03047 03048 // Allow extensions to add their custom variables to the mw.config map. 03049 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not 03050 // page-dependant but site-wide (without state). 03051 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. 03052 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) ); 03053 03054 // Merge in variables from addJsConfigVars last 03055 return array_merge( $vars, $this->mJsConfigVars ); 03056 } 03057 03067 public function userCanPreview() { 03068 if ( $this->getRequest()->getVal( 'action' ) != 'submit' 03069 || !$this->getRequest()->wasPosted() 03070 || !$this->getUser()->matchEditToken( 03071 $this->getRequest()->getVal( 'wpEditToken' ) ) 03072 ) { 03073 return false; 03074 } 03075 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) { 03076 return false; 03077 } 03078 03079 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) ); 03080 } 03081 03087 public function getHeadLinksArray( $addContentType = false ) { 03088 global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI, 03089 $wgSitename, $wgVersion, $wgHtml5, $wgMimeType, 03090 $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes, 03091 $wgDisableLangConversion, $wgCanonicalLanguageLinks, 03092 $wgRightsPage, $wgRightsUrl; 03093 03094 $tags = array(); 03095 03096 $canonicalUrl = $this->mCanonicalUrl; 03097 03098 if ( $addContentType ) { 03099 if ( $wgHtml5 ) { 03100 # More succinct than <meta http-equiv=Content-Type>, has the 03101 # same effect 03102 $tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) ); 03103 } else { 03104 $tags['meta-content-type'] = Html::element( 'meta', array( 03105 'http-equiv' => 'Content-Type', 03106 'content' => "$wgMimeType; charset=UTF-8" 03107 ) ); 03108 $tags['meta-content-style-type'] = Html::element( 'meta', array( // bug 15835 03109 'http-equiv' => 'Content-Style-Type', 03110 'content' => 'text/css' 03111 ) ); 03112 } 03113 } 03114 03115 $tags['meta-generator'] = Html::element( 'meta', array( 03116 'name' => 'generator', 03117 'content' => "MediaWiki $wgVersion", 03118 ) ); 03119 03120 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}"; 03121 if( $p !== 'index,follow' ) { 03122 // http://www.robotstxt.org/wc/meta-user.html 03123 // Only show if it's different from the default robots policy 03124 $tags['meta-robots'] = Html::element( 'meta', array( 03125 'name' => 'robots', 03126 'content' => $p, 03127 ) ); 03128 } 03129 03130 if ( count( $this->mKeywords ) > 0 ) { 03131 $strip = array( 03132 "/<.*?" . ">/" => '', 03133 "/_/" => ' ' 03134 ); 03135 $tags['meta-keywords'] = Html::element( 'meta', array( 03136 'name' => 'keywords', 03137 'content' => preg_replace( 03138 array_keys( $strip ), 03139 array_values( $strip ), 03140 implode( ',', $this->mKeywords ) 03141 ) 03142 ) ); 03143 } 03144 03145 foreach ( $this->mMetatags as $tag ) { 03146 if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) { 03147 $a = 'http-equiv'; 03148 $tag[0] = substr( $tag[0], 5 ); 03149 } else { 03150 $a = 'name'; 03151 } 03152 $tagName = "meta-{$tag[0]}"; 03153 if ( isset( $tags[$tagName] ) ) { 03154 $tagName .= $tag[1]; 03155 } 03156 $tags[$tagName] = Html::element( 'meta', 03157 array( 03158 $a => $tag[0], 03159 'content' => $tag[1] 03160 ) 03161 ); 03162 } 03163 03164 foreach ( $this->mLinktags as $tag ) { 03165 $tags[] = Html::element( 'link', $tag ); 03166 } 03167 03168 # Universal edit button 03169 if ( $wgUniversalEditButton && $this->isArticleRelated() ) { 03170 $user = $this->getUser(); 03171 if ( $this->getTitle()->quickUserCan( 'edit', $user ) 03172 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) { 03173 // Original UniversalEditButton 03174 $msg = $this->msg( 'edit' )->text(); 03175 $tags['universal-edit-button'] = Html::element( 'link', array( 03176 'rel' => 'alternate', 03177 'type' => 'application/x-wiki', 03178 'title' => $msg, 03179 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03180 ) ); 03181 // Alternate edit link 03182 $tags['alternative-edit'] = Html::element( 'link', array( 03183 'rel' => 'edit', 03184 'title' => $msg, 03185 'href' => $this->getTitle()->getLocalURL( 'action=edit' ) 03186 ) ); 03187 } 03188 } 03189 03190 # Generally the order of the favicon and apple-touch-icon links 03191 # should not matter, but Konqueror (3.5.9 at least) incorrectly 03192 # uses whichever one appears later in the HTML source. Make sure 03193 # apple-touch-icon is specified first to avoid this. 03194 if ( $wgAppleTouchIcon !== false ) { 03195 $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) ); 03196 } 03197 03198 if ( $wgFavicon !== false ) { 03199 $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) ); 03200 } 03201 03202 # OpenSearch description link 03203 $tags['opensearch'] = Html::element( 'link', array( 03204 'rel' => 'search', 03205 'type' => 'application/opensearchdescription+xml', 03206 'href' => wfScript( 'opensearch_desc' ), 03207 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(), 03208 ) ); 03209 03210 if ( $wgEnableAPI ) { 03211 # Real Simple Discovery link, provides auto-discovery information 03212 # for the MediaWiki API (and potentially additional custom API 03213 # support such as WordPress or Twitter-compatible APIs for a 03214 # blogging extension, etc) 03215 $tags['rsd'] = Html::element( 'link', array( 03216 'rel' => 'EditURI', 03217 'type' => 'application/rsd+xml', 03218 // Output a protocol-relative URL here if $wgServer is protocol-relative 03219 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though 03220 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ), 03221 ) ); 03222 } 03223 03224 # Language variants 03225 if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) { 03226 $lang = $this->getTitle()->getPageLanguage(); 03227 if ( $lang->hasVariants() ) { 03228 03229 $urlvar = $lang->getURLVariant(); 03230 03231 if ( !$urlvar ) { 03232 $variants = $lang->getVariants(); 03233 foreach ( $variants as $_v ) { 03234 $tags["variant-$_v"] = Html::element( 'link', array( 03235 'rel' => 'alternate', 03236 'hreflang' => $_v, 03237 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) ) 03238 ); 03239 } 03240 } else { 03241 $canonicalUrl = $this->getTitle()->getLocalURL(); 03242 } 03243 } 03244 } 03245 03246 # Copyright 03247 $copyright = ''; 03248 if ( $wgRightsPage ) { 03249 $copy = Title::newFromText( $wgRightsPage ); 03250 03251 if ( $copy ) { 03252 $copyright = $copy->getLocalURL(); 03253 } 03254 } 03255 03256 if ( !$copyright && $wgRightsUrl ) { 03257 $copyright = $wgRightsUrl; 03258 } 03259 03260 if ( $copyright ) { 03261 $tags['copyright'] = Html::element( 'link', array( 03262 'rel' => 'copyright', 03263 'href' => $copyright ) 03264 ); 03265 } 03266 03267 # Feeds 03268 if ( $wgFeed ) { 03269 foreach( $this->getSyndicationLinks() as $format => $link ) { 03270 # Use the page name for the title. In principle, this could 03271 # lead to issues with having the same name for different feeds 03272 # corresponding to the same page, but we can't avoid that at 03273 # this low a level. 03274 03275 $tags[] = $this->feedLink( 03276 $format, 03277 $link, 03278 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep) 03279 $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text() 03280 ); 03281 } 03282 03283 # Recent changes feed should appear on every page (except recentchanges, 03284 # that would be redundant). Put it after the per-page feed to avoid 03285 # changing existing behavior. It's still available, probably via a 03286 # menu in your browser. Some sites might have a different feed they'd 03287 # like to promote instead of the RC feed (maybe like a "Recent New Articles" 03288 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined. 03289 # If so, use it instead. 03290 if ( $wgOverrideSiteFeed ) { 03291 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) { 03292 // Note, this->feedLink escapes the url. 03293 $tags[] = $this->feedLink( 03294 $type, 03295 $feedUrl, 03296 $this->msg( "site-{$type}-feed", $wgSitename )->text() 03297 ); 03298 } 03299 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) { 03300 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' ); 03301 foreach ( $wgAdvertisedFeedTypes as $format ) { 03302 $tags[] = $this->feedLink( 03303 $format, 03304 $rctitle->getLocalURL( "feed={$format}" ), 03305 $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'. 03306 ); 03307 } 03308 } 03309 } 03310 03311 # Canonical URL 03312 global $wgEnableCanonicalServerLink; 03313 if ( $wgEnableCanonicalServerLink ) { 03314 if ( $canonicalUrl !== false ) { 03315 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL ); 03316 } else { 03317 $reqUrl = $this->getRequest()->getRequestURL(); 03318 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL ); 03319 } 03320 } 03321 if ( $canonicalUrl !== false ) { 03322 $tags[] = Html::element( 'link', array( 03323 'rel' => 'canonical', 03324 'href' => $canonicalUrl 03325 ) ); 03326 } 03327 03328 return $tags; 03329 } 03330 03337 public function getHeadLinks( $unused = null, $addContentType = false ) { 03338 return implode( "\n", $this->getHeadLinksArray( $addContentType ) ); 03339 } 03340 03349 private function feedLink( $type, $url, $text ) { 03350 return Html::element( 'link', array( 03351 'rel' => 'alternate', 03352 'type' => "application/$type+xml", 03353 'title' => $text, 03354 'href' => $url ) 03355 ); 03356 } 03357 03367 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) { 03368 $options = array(); 03369 // Even though we expect the media type to be lowercase, but here we 03370 // force it to lowercase to be safe. 03371 if( $media ) { 03372 $options['media'] = $media; 03373 } 03374 if( $condition ) { 03375 $options['condition'] = $condition; 03376 } 03377 if( $dir ) { 03378 $options['dir'] = $dir; 03379 } 03380 $this->styles[$style] = $options; 03381 } 03382 03388 public function addInlineStyle( $style_css, $flip = 'noflip' ) { 03389 if( $flip === 'flip' && $this->getLanguage()->isRTL() ) { 03390 # If wanted, and the interface is right-to-left, flip the CSS 03391 $style_css = CSSJanus::transform( $style_css, true, false ); 03392 } 03393 $this->mInlineStyles .= Html::inlineStyle( $style_css ); 03394 } 03395 03402 public function buildCssLinks() { 03403 global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, $wgContLang; 03404 03405 $this->getSkin()->setupSkinUserCss( $this ); 03406 03407 // Add ResourceLoader styles 03408 // Split the styles into four groups 03409 $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() ); 03410 $otherTags = ''; // Tags to append after the normal <link> tags 03411 $resourceLoader = $this->getResourceLoader(); 03412 03413 $moduleStyles = $this->getModuleStyles(); 03414 03415 // Per-site custom styles 03416 if ( $wgUseSiteCss ) { 03417 $moduleStyles[] = 'site'; 03418 $moduleStyles[] = 'noscript'; 03419 if( $this->getUser()->isLoggedIn() ) { 03420 $moduleStyles[] = 'user.groups'; 03421 } 03422 } 03423 03424 // Per-user custom styles 03425 if ( $wgAllowUserCss ) { 03426 if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) { 03427 // We're on a preview of a CSS subpage 03428 // Exclude this page from the user module in case it's in there (bug 26283) 03429 $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false, 03430 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ) 03431 ); 03432 03433 // Load the previewed CSS 03434 // If needed, Janus it first. This is user-supplied CSS, so it's 03435 // assumed to be right for the content language directionality. 03436 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' ); 03437 if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) { 03438 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false ); 03439 } 03440 $otherTags .= Html::inlineStyle( $previewedCSS ); 03441 } else { 03442 // Load the user styles normally 03443 $moduleStyles[] = 'user'; 03444 } 03445 } 03446 03447 // Per-user preference styles 03448 if ( $wgAllowUserCssPrefs ) { 03449 $moduleStyles[] = 'user.cssprefs'; 03450 } 03451 03452 foreach ( $moduleStyles as $name ) { 03453 $module = $resourceLoader->getModule( $name ); 03454 if ( !$module ) { 03455 continue; 03456 } 03457 $group = $module->getGroup(); 03458 // Modules in groups named "other" or anything different than "user", "site" or "private" 03459 // will be placed in the "other" group 03460 $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name; 03461 } 03462 03463 // We want site, private and user styles to override dynamically added styles from modules, but we want 03464 // dynamically added styles to override statically added styles from other modules. So the order 03465 // has to be other, dynamic, site, private, user 03466 // Add statically added styles for other modules 03467 $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES ); 03468 // Add normal styles added through addStyle()/addInlineStyle() here 03469 $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles; 03470 // Add marker tag to mark the place where the client-side loader should inject dynamic styles 03471 // We use a <meta> tag with a made-up name for this because that's valid HTML 03472 $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n"; 03473 03474 // Add site, private and user styles 03475 // 'private' at present only contains user.options, so put that before 'user' 03476 // Any future private modules will likely have a similar user-specific character 03477 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) { 03478 $ret .= $this->makeResourceLoaderLink( $styles[$group], 03479 ResourceLoaderModule::TYPE_STYLES 03480 ); 03481 } 03482 03483 // Add stuff in $otherTags (previewed user CSS if applicable) 03484 $ret .= $otherTags; 03485 return $ret; 03486 } 03487 03491 public function buildCssLinksArray() { 03492 $links = array(); 03493 03494 // Add any extension CSS 03495 foreach ( $this->mExtStyles as $url ) { 03496 $this->addStyle( $url ); 03497 } 03498 $this->mExtStyles = array(); 03499 03500 foreach( $this->styles as $file => $options ) { 03501 $link = $this->styleLink( $file, $options ); 03502 if( $link ) { 03503 $links[$file] = $link; 03504 } 03505 } 03506 return $links; 03507 } 03508 03517 protected function styleLink( $style, $options ) { 03518 if( isset( $options['dir'] ) ) { 03519 if( $this->getLanguage()->getDir() != $options['dir'] ) { 03520 return ''; 03521 } 03522 } 03523 03524 if( isset( $options['media'] ) ) { 03525 $media = self::transformCssMedia( $options['media'] ); 03526 if( is_null( $media ) ) { 03527 return ''; 03528 } 03529 } else { 03530 $media = 'all'; 03531 } 03532 03533 if( substr( $style, 0, 1 ) == '/' || 03534 substr( $style, 0, 5 ) == 'http:' || 03535 substr( $style, 0, 6 ) == 'https:' ) { 03536 $url = $style; 03537 } else { 03538 global $wgStylePath, $wgStyleVersion; 03539 $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion; 03540 } 03541 03542 $link = Html::linkedStyle( $url, $media ); 03543 03544 if( isset( $options['condition'] ) ) { 03545 $condition = htmlspecialchars( $options['condition'] ); 03546 $link = "<!--[if $condition]>$link<![endif]-->"; 03547 } 03548 return $link; 03549 } 03550 03558 public static function transformCssMedia( $media ) { 03559 global $wgRequest, $wgHandheldForIPhone; 03560 03561 // http://www.w3.org/TR/css3-mediaqueries/#syntax 03562 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i'; 03563 03564 // Switch in on-screen display for media testing 03565 $switches = array( 03566 'printable' => 'print', 03567 'handheld' => 'handheld', 03568 ); 03569 foreach( $switches as $switch => $targetMedia ) { 03570 if( $wgRequest->getBool( $switch ) ) { 03571 if( $media == $targetMedia ) { 03572 $media = ''; 03573 } elseif( preg_match( $screenMediaQueryRegex, $media ) === 1 ) { 03574 // This regex will not attempt to understand a comma-separated media_query_list 03575 // 03576 // Example supported values for $media: 'screen', 'only screen', 'screen and (min-width: 982px)' ), 03577 // Example NOT supported value for $media: '3d-glasses, screen, print and resolution > 90dpi' 03578 // 03579 // If it's a print request, we never want any kind of screen stylesheets 03580 // If it's a handheld request (currently the only other choice with a switch), 03581 // we don't want simple 'screen' but we might want screen queries that 03582 // have a max-width or something, so we'll pass all others on and let the 03583 // client do the query. 03584 if( $targetMedia == 'print' || $media == 'screen' ) { 03585 return null; 03586 } 03587 } 03588 } 03589 } 03590 03591 // Expand longer media queries as iPhone doesn't grok 'handheld' 03592 if( $wgHandheldForIPhone ) { 03593 $mediaAliases = array( 03594 'screen' => 'screen and (min-device-width: 481px)', 03595 'handheld' => 'handheld, only screen and (max-device-width: 480px)', 03596 ); 03597 03598 if( isset( $mediaAliases[$media] ) ) { 03599 $media = $mediaAliases[$media]; 03600 } 03601 } 03602 03603 return $media; 03604 } 03605 03612 public function addWikiMsg( /*...*/ ) { 03613 $args = func_get_args(); 03614 $name = array_shift( $args ); 03615 $this->addWikiMsgArray( $name, $args ); 03616 } 03617 03626 public function addWikiMsgArray( $name, $args ) { 03627 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() ); 03628 } 03629 03653 public function wrapWikiMsg( $wrap /*, ...*/ ) { 03654 $msgSpecs = func_get_args(); 03655 array_shift( $msgSpecs ); 03656 $msgSpecs = array_values( $msgSpecs ); 03657 $s = $wrap; 03658 foreach ( $msgSpecs as $n => $spec ) { 03659 if ( is_array( $spec ) ) { 03660 $args = $spec; 03661 $name = array_shift( $args ); 03662 if ( isset( $args['options'] ) ) { 03663 unset( $args['options'] ); 03664 wfDeprecated( 03665 'Adding "options" to ' . __METHOD__ . ' is no longer supported', 03666 '1.20' 03667 ); 03668 } 03669 } else { 03670 $args = array(); 03671 $name = $spec; 03672 } 03673 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s ); 03674 } 03675 $this->addWikiText( $s ); 03676 } 03677 03687 public function includeJQuery( $modules = array() ) { 03688 return array(); 03689 } 03690 03691 }