MediaWiki
REL1_24
|
00001 <?php 00038 class OutputPage extends ContextSource { 00040 protected $mMetatags = array(); 00041 00043 protected $mLinktags = array(); 00044 00046 protected $mCanonicalUrl = false; 00047 00052 protected $mExtStyles = array(); 00053 00057 public $mPagetitle = ''; 00058 00063 public $mBodytext = ''; 00064 00070 public $mDebugtext = ''; 00071 00073 private $mHTMLtitle = ''; 00074 00079 private $mIsarticle = false; 00080 00082 private $mIsArticleRelated = true; 00083 00088 private $mPrintable = false; 00089 00094 private $mSubtitle = array(); 00095 00097 public $mRedirect = ''; 00098 00100 protected $mStatusCode; 00101 00106 protected $mLastModified = ''; 00107 00117 private $mETag = false; 00118 00120 protected $mCategoryLinks = array(); 00121 00123 protected $mCategories = array(); 00124 00126 private $mLanguageLinks = array(); 00127 00134 private $mScripts = ''; 00135 00137 protected $mInlineStyles = ''; 00138 00140 private $mLinkColours; 00141 00146 public $mPageLinkTitle = ''; 00147 00149 protected $mHeadItems = array(); 00150 00151 // @todo FIXME: Next 5 variables probably come from the resource loader 00152 00154 protected $mModules = array(); 00155 00157 protected $mModuleScripts = array(); 00158 00160 protected $mModuleStyles = array(); 00161 00163 protected $mModuleMessages = array(); 00164 00166 protected $mResourceLoader; 00167 00169 protected $mJsConfigVars = array(); 00170 00172 protected $mTemplateIds = array(); 00173 00175 protected $mImageTimeKeys = array(); 00176 00178 public $mRedirectCode = ''; 00179 00180 protected $mFeedLinksAppendQuery = null; 00181 00187 protected $mAllowedModuleOrigin = ResourceLoaderModule::ORIGIN_ALL; 00188 00190 protected $mDoNothing = false; 00191 00192 // Parser related. 00193 00198 private $mContainsOldMagic = 0; 00199 00201 protected $mContainsNewMagic = 0; 00202 00207 protected $mParserOptions = null; 00208 00214 private $mFeedLinks = array(); 00215 00216 // Gwicke work on squid caching? Roughly from 2003. 00217 protected $mEnableClientCache = true; 00218 00220 private $mArticleBodyOnly = false; 00221 00223 protected $mNewSectionLink = false; 00224 00226 protected $mHideNewSectionLink = false; 00227 00233 public $mNoGallery = false; 00234 00236 private $mPageTitleActionText = ''; 00237 00239 private $mParseWarnings = array(); 00240 00242 protected $mSquidMaxage = 0; 00243 00248 protected $mPreventClickjacking = true; 00249 00251 private $mRevisionId = null; 00252 00254 private $mRevisionTimestamp = null; 00255 00257 protected $mFileVersion = null; 00258 00267 protected $styles = array(); 00268 00272 protected $mJQueryDone = false; 00273 00274 private $mIndexPolicy = 'index'; 00275 private $mFollowPolicy = 'follow'; 00276 private $mVaryHeader = array( 00277 'Accept-Encoding' => array( 'list-contains=gzip' ), 00278 ); 00279 00286 private $mRedirectedFrom = null; 00287 00291 private $mProperties = array(); 00292 00296 private $mTarget = null; 00297 00301 private $mEnableTOC = true; 00302 00306 private $mEnableSectionEditLinks = true; 00307 00314 function __construct( IContextSource $context = null ) { 00315 if ( $context === null ) { 00316 # Extensions should use `new RequestContext` instead of `new OutputPage` now. 00317 wfDeprecated( __METHOD__, '1.18' ); 00318 } else { 00319 $this->setContext( $context ); 00320 } 00321 } 00322 00329 public function redirect( $url, $responsecode = '302' ) { 00330 # Strip newlines as a paranoia check for header injection in PHP<5.1.2 00331 $this->mRedirect = str_replace( "\n", '', $url ); 00332 $this->mRedirectCode = $responsecode; 00333 } 00334 00340 public function getRedirect() { 00341 return $this->mRedirect; 00342 } 00343 00349 public function setStatusCode( $statusCode ) { 00350 $this->mStatusCode = $statusCode; 00351 } 00352 00360 function addMeta( $name, $val ) { 00361 array_push( $this->mMetatags, array( $name, $val ) ); 00362 } 00363 00371 function addLink( array $linkarr ) { 00372 array_push( $this->mLinktags, $linkarr ); 00373 } 00374 00382 function addMetadataLink( array $linkarr ) { 00383 $linkarr['rel'] = $this->getMetadataAttribute(); 00384 $this->addLink( $linkarr ); 00385 } 00386 00392 function setCanonicalUrl( $url ) { 00393 $this->mCanonicalUrl = $url; 00394 } 00395 00401 public function getMetadataAttribute() { 00402 # note: buggy CC software only reads first "meta" link 00403 static $haveMeta = false; 00404 if ( $haveMeta ) { 00405 return 'alternate meta'; 00406 } else { 00407 $haveMeta = true; 00408 return 'meta'; 00409 } 00410 } 00411 00417 function addScript( $script ) { 00418 $this->mScripts .= $script . "\n"; 00419 } 00420 00429 public function addExtensionStyle( $url ) { 00430 array_push( $this->mExtStyles, $url ); 00431 } 00432 00438 function getExtStyle() { 00439 return $this->mExtStyles; 00440 } 00441 00449 public function addScriptFile( $file, $version = null ) { 00450 // See if $file parameter is an absolute URL or begins with a slash 00451 if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) { 00452 $path = $file; 00453 } else { 00454 $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}"; 00455 } 00456 if ( is_null( $version ) ) { 00457 $version = $this->getConfig()->get( 'StyleVersion' ); 00458 } 00459 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) ); 00460 } 00461 00467 public function addInlineScript( $script ) { 00468 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n"; 00469 } 00470 00477 function getScript() { 00478 wfDeprecated( __METHOD__, '1.24' ); 00479 return $this->mScripts . $this->getHeadItems(); 00480 } 00481 00490 protected function filterModules( array $modules, $position = null, 00491 $type = ResourceLoaderModule::TYPE_COMBINED 00492 ) { 00493 $resourceLoader = $this->getResourceLoader(); 00494 $filteredModules = array(); 00495 foreach ( $modules as $val ) { 00496 $module = $resourceLoader->getModule( $val ); 00497 if ( $module instanceof ResourceLoaderModule 00498 && $module->getOrigin() <= $this->getAllowedModules( $type ) 00499 && ( is_null( $position ) || $module->getPosition() == $position ) 00500 && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) ) 00501 ) { 00502 $filteredModules[] = $val; 00503 } 00504 } 00505 return $filteredModules; 00506 } 00507 00516 public function getModules( $filter = false, $position = null, $param = 'mModules' ) { 00517 $modules = array_values( array_unique( $this->$param ) ); 00518 return $filter 00519 ? $this->filterModules( $modules, $position ) 00520 : $modules; 00521 } 00522 00530 public function addModules( $modules ) { 00531 $this->mModules = array_merge( $this->mModules, (array)$modules ); 00532 } 00533 00542 public function getModuleScripts( $filter = false, $position = null ) { 00543 return $this->getModules( $filter, $position, 'mModuleScripts' ); 00544 } 00545 00553 public function addModuleScripts( $modules ) { 00554 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules ); 00555 } 00556 00565 public function getModuleStyles( $filter = false, $position = null ) { 00566 return $this->getModules( $filter, $position, 'mModuleStyles' ); 00567 } 00568 00578 public function addModuleStyles( $modules ) { 00579 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules ); 00580 } 00581 00590 public function getModuleMessages( $filter = false, $position = null ) { 00591 return $this->getModules( $filter, $position, 'mModuleMessages' ); 00592 } 00593 00601 public function addModuleMessages( $modules ) { 00602 $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules ); 00603 } 00604 00608 public function getTarget() { 00609 return $this->mTarget; 00610 } 00611 00617 public function setTarget( $target ) { 00618 $this->mTarget = $target; 00619 } 00620 00626 function getHeadItemsArray() { 00627 return $this->mHeadItems; 00628 } 00629 00637 function getHeadItems() { 00638 wfDeprecated( __METHOD__, '1.24' ); 00639 $s = ''; 00640 foreach ( $this->mHeadItems as $item ) { 00641 $s .= $item; 00642 } 00643 return $s; 00644 } 00645 00652 public function addHeadItem( $name, $value ) { 00653 $this->mHeadItems[$name] = $value; 00654 } 00655 00662 public function hasHeadItem( $name ) { 00663 return isset( $this->mHeadItems[$name] ); 00664 } 00665 00671 function setETag( $tag ) { 00672 $this->mETag = $tag; 00673 } 00674 00682 public function setArticleBodyOnly( $only ) { 00683 $this->mArticleBodyOnly = $only; 00684 } 00685 00691 public function getArticleBodyOnly() { 00692 return $this->mArticleBodyOnly; 00693 } 00694 00702 public function setProperty( $name, $value ) { 00703 $this->mProperties[$name] = $value; 00704 } 00705 00713 public function getProperty( $name ) { 00714 if ( isset( $this->mProperties[$name] ) ) { 00715 return $this->mProperties[$name]; 00716 } else { 00717 return null; 00718 } 00719 } 00720 00732 public function checkLastModified( $timestamp ) { 00733 if ( !$timestamp || $timestamp == '19700101000000' ) { 00734 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" ); 00735 return false; 00736 } 00737 $config = $this->getConfig(); 00738 if ( !$config->get( 'CachePages' ) ) { 00739 wfDebug( __METHOD__ . ": CACHE DISABLED\n" ); 00740 return false; 00741 } 00742 00743 $timestamp = wfTimestamp( TS_MW, $timestamp ); 00744 $modifiedTimes = array( 00745 'page' => $timestamp, 00746 'user' => $this->getUser()->getTouched(), 00747 'epoch' => $config->get( 'CacheEpoch' ) 00748 ); 00749 if ( $config->get( 'UseSquid' ) ) { 00750 // bug 44570: the core page itself may not change, but resources might 00751 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) ); 00752 } 00753 wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) ); 00754 00755 $maxModified = max( $modifiedTimes ); 00756 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified ); 00757 00758 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' ); 00759 if ( $clientHeader === false ) { 00760 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", 'log' ); 00761 return false; 00762 } 00763 00764 # IE sends sizes after the date like this: 00765 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202 00766 # this breaks strtotime(). 00767 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader ); 00768 00769 wfSuppressWarnings(); // E_STRICT system time bitching 00770 $clientHeaderTime = strtotime( $clientHeader ); 00771 wfRestoreWarnings(); 00772 if ( !$clientHeaderTime ) { 00773 wfDebug( __METHOD__ 00774 . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" ); 00775 return false; 00776 } 00777 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime ); 00778 00779 # Make debug info 00780 $info = ''; 00781 foreach ( $modifiedTimes as $name => $value ) { 00782 if ( $info !== '' ) { 00783 $info .= ', '; 00784 } 00785 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value ); 00786 } 00787 00788 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " . 00789 wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", 'log' ); 00790 wfDebug( __METHOD__ . ": effective Last-Modified: " . 00791 wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", 'log' ); 00792 if ( $clientHeaderTime < $maxModified ) { 00793 wfDebug( __METHOD__ . ": STALE, $info\n", 'log' ); 00794 return false; 00795 } 00796 00797 # Not modified 00798 # Give a 304 response code and disable body output 00799 wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", 'log' ); 00800 ini_set( 'zlib.output_compression', 0 ); 00801 $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" ); 00802 $this->sendCacheControl(); 00803 $this->disable(); 00804 00805 // Don't output a compressed blob when using ob_gzhandler; 00806 // it's technically against HTTP spec and seems to confuse 00807 // Firefox when the response gets split over two packets. 00808 wfClearOutputBuffers(); 00809 00810 return true; 00811 } 00812 00819 public function setLastModified( $timestamp ) { 00820 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp ); 00821 } 00822 00831 public function setRobotPolicy( $policy ) { 00832 $policy = Article::formatRobotPolicy( $policy ); 00833 00834 if ( isset( $policy['index'] ) ) { 00835 $this->setIndexPolicy( $policy['index'] ); 00836 } 00837 if ( isset( $policy['follow'] ) ) { 00838 $this->setFollowPolicy( $policy['follow'] ); 00839 } 00840 } 00841 00849 public function setIndexPolicy( $policy ) { 00850 $policy = trim( $policy ); 00851 if ( in_array( $policy, array( 'index', 'noindex' ) ) ) { 00852 $this->mIndexPolicy = $policy; 00853 } 00854 } 00855 00863 public function setFollowPolicy( $policy ) { 00864 $policy = trim( $policy ); 00865 if ( in_array( $policy, array( 'follow', 'nofollow' ) ) ) { 00866 $this->mFollowPolicy = $policy; 00867 } 00868 } 00869 00876 public function setPageTitleActionText( $text ) { 00877 $this->mPageTitleActionText = $text; 00878 } 00879 00885 public function getPageTitleActionText() { 00886 return $this->mPageTitleActionText; 00887 } 00888 00895 public function setHTMLTitle( $name ) { 00896 if ( $name instanceof Message ) { 00897 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text(); 00898 } else { 00899 $this->mHTMLtitle = $name; 00900 } 00901 } 00902 00908 public function getHTMLTitle() { 00909 return $this->mHTMLtitle; 00910 } 00911 00917 public function setRedirectedFrom( $t ) { 00918 $this->mRedirectedFrom = $t; 00919 } 00920 00931 public function setPageTitle( $name ) { 00932 if ( $name instanceof Message ) { 00933 $name = $name->setContext( $this->getContext() )->text(); 00934 } 00935 00936 # change "<script>foo&bar</script>" to "<script>foo&bar</script>" 00937 # but leave "<i>foobar</i>" alone 00938 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) ); 00939 $this->mPagetitle = $nameWithTags; 00940 00941 # change "<i>foo&bar</i>" to "foo&bar" 00942 $this->setHTMLTitle( 00943 $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) 00944 ->inContentLanguage() 00945 ); 00946 } 00947 00953 public function getPageTitle() { 00954 return $this->mPagetitle; 00955 } 00956 00962 public function setTitle( Title $t ) { 00963 $this->getContext()->setTitle( $t ); 00964 } 00965 00971 public function setSubtitle( $str ) { 00972 $this->clearSubtitle(); 00973 $this->addSubtitle( $str ); 00974 } 00975 00982 public function appendSubtitle( $str ) { 00983 $this->addSubtitle( $str ); 00984 } 00985 00991 public function addSubtitle( $str ) { 00992 if ( $str instanceof Message ) { 00993 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse(); 00994 } else { 00995 $this->mSubtitle[] = $str; 00996 } 00997 } 00998 01005 public function addBacklinkSubtitle( Title $title, $query = array() ) { 01006 if ( $title->isRedirect() ) { 01007 $query['redirect'] = 'no'; 01008 } 01009 $this->addSubtitle( $this->msg( 'backlinksubtitle' ) 01010 ->rawParams( Linker::link( $title, null, array(), $query ) ) ); 01011 } 01012 01016 public function clearSubtitle() { 01017 $this->mSubtitle = array(); 01018 } 01019 01025 public function getSubtitle() { 01026 return implode( "<br />\n\t\t\t\t", $this->mSubtitle ); 01027 } 01028 01033 public function setPrintable() { 01034 $this->mPrintable = true; 01035 } 01036 01042 public function isPrintable() { 01043 return $this->mPrintable; 01044 } 01045 01049 public function disable() { 01050 $this->mDoNothing = true; 01051 } 01052 01058 public function isDisabled() { 01059 return $this->mDoNothing; 01060 } 01061 01067 public function showNewSectionLink() { 01068 return $this->mNewSectionLink; 01069 } 01070 01076 public function forceHideNewSectionLink() { 01077 return $this->mHideNewSectionLink; 01078 } 01079 01088 public function setSyndicated( $show = true ) { 01089 if ( $show ) { 01090 $this->setFeedAppendQuery( false ); 01091 } else { 01092 $this->mFeedLinks = array(); 01093 } 01094 } 01095 01105 public function setFeedAppendQuery( $val ) { 01106 $this->mFeedLinks = array(); 01107 01108 foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) { 01109 $query = "feed=$type"; 01110 if ( is_string( $val ) ) { 01111 $query .= '&' . $val; 01112 } 01113 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query ); 01114 } 01115 } 01116 01123 public function addFeedLink( $format, $href ) { 01124 if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) { 01125 $this->mFeedLinks[$format] = $href; 01126 } 01127 } 01128 01133 public function isSyndicated() { 01134 return count( $this->mFeedLinks ) > 0; 01135 } 01136 01141 public function getSyndicationLinks() { 01142 return $this->mFeedLinks; 01143 } 01144 01150 public function getFeedAppendQuery() { 01151 return $this->mFeedLinksAppendQuery; 01152 } 01153 01161 public function setArticleFlag( $v ) { 01162 $this->mIsarticle = $v; 01163 if ( $v ) { 01164 $this->mIsArticleRelated = $v; 01165 } 01166 } 01167 01174 public function isArticle() { 01175 return $this->mIsarticle; 01176 } 01177 01184 public function setArticleRelated( $v ) { 01185 $this->mIsArticleRelated = $v; 01186 if ( !$v ) { 01187 $this->mIsarticle = false; 01188 } 01189 } 01190 01196 public function isArticleRelated() { 01197 return $this->mIsArticleRelated; 01198 } 01199 01206 public function addLanguageLinks( array $newLinkArray ) { 01207 $this->mLanguageLinks += $newLinkArray; 01208 } 01209 01216 public function setLanguageLinks( array $newLinkArray ) { 01217 $this->mLanguageLinks = $newLinkArray; 01218 } 01219 01225 public function getLanguageLinks() { 01226 return $this->mLanguageLinks; 01227 } 01228 01234 public function addCategoryLinks( array $categories ) { 01235 global $wgContLang; 01236 01237 if ( !is_array( $categories ) || count( $categories ) == 0 ) { 01238 return; 01239 } 01240 01241 # Add the links to a LinkBatch 01242 $arr = array( NS_CATEGORY => $categories ); 01243 $lb = new LinkBatch; 01244 $lb->setArray( $arr ); 01245 01246 # Fetch existence plus the hiddencat property 01247 $dbr = wfGetDB( DB_SLAVE ); 01248 $fields = array( 'page_id', 'page_namespace', 'page_title', 'page_len', 01249 'page_is_redirect', 'page_latest', 'pp_value' ); 01250 01251 if ( $this->getConfig()->get( 'ContentHandlerUseDB' ) ) { 01252 $fields[] = 'page_content_model'; 01253 } 01254 01255 $res = $dbr->select( array( 'page', 'page_props' ), 01256 $fields, 01257 $lb->constructSet( 'page', $dbr ), 01258 __METHOD__, 01259 array(), 01260 array( 'page_props' => array( 'LEFT JOIN', array( 01261 'pp_propname' => 'hiddencat', 01262 'pp_page = page_id' 01263 ) ) ) 01264 ); 01265 01266 # Add the results to the link cache 01267 $lb->addResultToCache( LinkCache::singleton(), $res ); 01268 01269 # Set all the values to 'normal'. 01270 $categories = array_fill_keys( array_keys( $categories ), 'normal' ); 01271 01272 # Mark hidden categories 01273 foreach ( $res as $row ) { 01274 if ( isset( $row->pp_value ) ) { 01275 $categories[$row->page_title] = 'hidden'; 01276 } 01277 } 01278 01279 # Add the remaining categories to the skin 01280 if ( wfRunHooks( 01281 'OutputPageMakeCategoryLinks', 01282 array( &$this, $categories, &$this->mCategoryLinks ) ) 01283 ) { 01284 foreach ( $categories as $category => $type ) { 01285 $origcategory = $category; 01286 $title = Title::makeTitleSafe( NS_CATEGORY, $category ); 01287 if ( !$title ) { 01288 continue; 01289 } 01290 $wgContLang->findVariantLink( $category, $title, true ); 01291 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) { 01292 continue; 01293 } 01294 $text = $wgContLang->convertHtml( $title->getText() ); 01295 $this->mCategories[] = $title->getText(); 01296 $this->mCategoryLinks[$type][] = Linker::link( $title, $text ); 01297 } 01298 } 01299 } 01300 01306 public function setCategoryLinks( array $categories ) { 01307 $this->mCategoryLinks = array(); 01308 $this->addCategoryLinks( $categories ); 01309 } 01310 01319 public function getCategoryLinks() { 01320 return $this->mCategoryLinks; 01321 } 01322 01328 public function getCategories() { 01329 return $this->mCategories; 01330 } 01331 01338 public function disallowUserJs() { 01339 $this->reduceAllowedModuleOrigin( ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL ); 01340 } 01341 01350 public function getAllowedModules( $type = null ) { 01351 return $this->mAllowedModuleOrigin; 01352 } 01353 01363 public function setAllowedModules( $type, $level ) { 01364 wfDeprecated( __METHOD__, '1.24' ); 01365 $this->reduceAllowedModuleOrigin( $level ); 01366 } 01367 01377 public function reduceAllowedModules( $type, $level ) { 01378 wfDeprecated( __METHOD__, '1.24' ); 01379 $this->reduceAllowedModuleOrigin( $level ); 01380 } 01381 01390 public function reduceAllowedModuleOrigin( $level ) { 01391 $this->mAllowedModuleOrigin = min( $this->mAllowedModuleOrigin, $level ); 01392 } 01393 01399 public function prependHTML( $text ) { 01400 $this->mBodytext = $text . $this->mBodytext; 01401 } 01402 01408 public function addHTML( $text ) { 01409 $this->mBodytext .= $text; 01410 } 01411 01421 public function addElement( $element, array $attribs = array(), $contents = '' ) { 01422 $this->addHTML( Html::element( $element, $attribs, $contents ) ); 01423 } 01424 01428 public function clearHTML() { 01429 $this->mBodytext = ''; 01430 } 01431 01437 public function getHTML() { 01438 return $this->mBodytext; 01439 } 01440 01448 public function parserOptions( $options = null ) { 01449 if ( !$this->mParserOptions ) { 01450 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() ); 01451 $this->mParserOptions->setEditSection( false ); 01452 } 01453 return wfSetVar( $this->mParserOptions, $options ); 01454 } 01455 01463 public function setRevisionId( $revid ) { 01464 $val = is_null( $revid ) ? null : intval( $revid ); 01465 return wfSetVar( $this->mRevisionId, $val ); 01466 } 01467 01473 public function getRevisionId() { 01474 return $this->mRevisionId; 01475 } 01476 01484 public function setRevisionTimestamp( $timestamp ) { 01485 return wfSetVar( $this->mRevisionTimestamp, $timestamp ); 01486 } 01487 01494 public function getRevisionTimestamp() { 01495 return $this->mRevisionTimestamp; 01496 } 01497 01504 public function setFileVersion( $file ) { 01505 $val = null; 01506 if ( $file instanceof File && $file->exists() ) { 01507 $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ); 01508 } 01509 return wfSetVar( $this->mFileVersion, $val, true ); 01510 } 01511 01517 public function getFileVersion() { 01518 return $this->mFileVersion; 01519 } 01520 01527 public function getTemplateIds() { 01528 return $this->mTemplateIds; 01529 } 01530 01537 public function getFileSearchOptions() { 01538 return $this->mImageTimeKeys; 01539 } 01540 01549 public function addWikiText( $text, $linestart = true, $interface = true ) { 01550 $title = $this->getTitle(); // Work around E_STRICT 01551 if ( !$title ) { 01552 throw new MWException( 'Title is null' ); 01553 } 01554 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface ); 01555 } 01556 01564 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) { 01565 $this->addWikiTextTitle( $text, $title, $linestart ); 01566 } 01567 01575 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) { 01576 $this->addWikiTextTitle( $text, $title, $linestart, true ); 01577 } 01578 01585 public function addWikiTextTidy( $text, $linestart = true ) { 01586 $title = $this->getTitle(); 01587 $this->addWikiTextTitleTidy( $text, $title, $linestart ); 01588 } 01589 01600 public function addWikiTextTitle( $text, Title $title, $linestart, 01601 $tidy = false, $interface = false 01602 ) { 01603 global $wgParser; 01604 01605 wfProfileIn( __METHOD__ ); 01606 01607 $popts = $this->parserOptions(); 01608 $oldTidy = $popts->setTidy( $tidy ); 01609 $popts->setInterfaceMessage( (bool)$interface ); 01610 01611 $parserOutput = $wgParser->getFreshParser()->parse( 01612 $text, $title, $popts, 01613 $linestart, true, $this->mRevisionId 01614 ); 01615 01616 $popts->setTidy( $oldTidy ); 01617 01618 $this->addParserOutput( $parserOutput ); 01619 01620 wfProfileOut( __METHOD__ ); 01621 } 01622 01629 public function addParserOutputNoText( $parserOutput ) { 01630 $this->addParserOutputMetadata( $parserOutput ); 01631 } 01632 01641 public function addParserOutputMetadata( $parserOutput ) { 01642 $this->mLanguageLinks += $parserOutput->getLanguageLinks(); 01643 $this->addCategoryLinks( $parserOutput->getCategories() ); 01644 $this->mNewSectionLink = $parserOutput->getNewSection(); 01645 $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); 01646 01647 $this->mParseWarnings = $parserOutput->getWarnings(); 01648 if ( !$parserOutput->isCacheable() ) { 01649 $this->enableClientCache( false ); 01650 } 01651 $this->mNoGallery = $parserOutput->getNoGallery(); 01652 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() ); 01653 $this->addModules( $parserOutput->getModules() ); 01654 $this->addModuleScripts( $parserOutput->getModuleScripts() ); 01655 $this->addModuleStyles( $parserOutput->getModuleStyles() ); 01656 $this->addModuleMessages( $parserOutput->getModuleMessages() ); 01657 $this->addJsConfigVars( $parserOutput->getJsConfigVars() ); 01658 $this->mPreventClickjacking = $this->mPreventClickjacking 01659 || $parserOutput->preventClickjacking(); 01660 01661 // Template versioning... 01662 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) { 01663 if ( isset( $this->mTemplateIds[$ns] ) ) { 01664 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns]; 01665 } else { 01666 $this->mTemplateIds[$ns] = $dbks; 01667 } 01668 } 01669 // File versioning... 01670 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) { 01671 $this->mImageTimeKeys[$dbk] = $data; 01672 } 01673 01674 // Hooks registered in the object 01675 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' ); 01676 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) { 01677 list( $hookName, $data ) = $hookInfo; 01678 if ( isset( $parserOutputHooks[$hookName] ) ) { 01679 call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data ); 01680 } 01681 } 01682 01683 // Link flags are ignored for now, but may in the future be 01684 // used to mark individual language links. 01685 $linkFlags = array(); 01686 wfRunHooks( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) ); 01687 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); 01688 } 01689 01697 public function addParserOutputContent( $parserOutput ) { 01698 $this->addParserOutputText( $parserOutput ); 01699 01700 $this->addModules( $parserOutput->getModules() ); 01701 $this->addModuleScripts( $parserOutput->getModuleScripts() ); 01702 $this->addModuleStyles( $parserOutput->getModuleStyles() ); 01703 $this->addModuleMessages( $parserOutput->getModuleMessages() ); 01704 01705 $this->addJsConfigVars( $parserOutput->getJsConfigVars() ); 01706 } 01707 01714 public function addParserOutputText( $parserOutput ) { 01715 $text = $parserOutput->getText(); 01716 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) ); 01717 $this->addHTML( $text ); 01718 } 01719 01725 function addParserOutput( $parserOutput ) { 01726 $this->addParserOutputMetadata( $parserOutput ); 01727 $parserOutput->setTOCEnabled( $this->mEnableTOC ); 01728 01729 // Touch section edit links only if not previously disabled 01730 if ( $parserOutput->getEditSectionTokens() ) { 01731 $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks ); 01732 } 01733 01734 $this->addParserOutputText( $parserOutput ); 01735 } 01736 01742 public function addTemplate( &$template ) { 01743 $this->addHTML( $template->getHTML() ); 01744 } 01745 01758 public function parse( $text, $linestart = true, $interface = false, $language = null ) { 01759 global $wgParser; 01760 01761 if ( is_null( $this->getTitle() ) ) { 01762 throw new MWException( 'Empty $mTitle in ' . __METHOD__ ); 01763 } 01764 01765 $popts = $this->parserOptions(); 01766 if ( $interface ) { 01767 $popts->setInterfaceMessage( true ); 01768 } 01769 if ( $language !== null ) { 01770 $oldLang = $popts->setTargetLanguage( $language ); 01771 } 01772 01773 $parserOutput = $wgParser->getFreshParser()->parse( 01774 $text, $this->getTitle(), $popts, 01775 $linestart, true, $this->mRevisionId 01776 ); 01777 01778 if ( $interface ) { 01779 $popts->setInterfaceMessage( false ); 01780 } 01781 if ( $language !== null ) { 01782 $popts->setTargetLanguage( $oldLang ); 01783 } 01784 01785 return $parserOutput->getText(); 01786 } 01787 01798 public function parseInline( $text, $linestart = true, $interface = false ) { 01799 $parsed = $this->parse( $text, $linestart, $interface ); 01800 return Parser::stripOuterParagraph( $parsed ); 01801 } 01802 01808 public function setSquidMaxage( $maxage ) { 01809 $this->mSquidMaxage = $maxage; 01810 } 01811 01819 public function enableClientCache( $state ) { 01820 return wfSetVar( $this->mEnableClientCache, $state ); 01821 } 01822 01828 function getCacheVaryCookies() { 01829 static $cookies; 01830 if ( $cookies === null ) { 01831 $config = $this->getConfig(); 01832 $cookies = array_merge( 01833 array( 01834 $config->get( 'CookiePrefix' ) . 'Token', 01835 $config->get( 'CookiePrefix' ) . 'LoggedOut', 01836 "forceHTTPS", 01837 session_name() 01838 ), 01839 $config->get( 'CacheVaryCookies' ) 01840 ); 01841 wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) ); 01842 } 01843 return $cookies; 01844 } 01845 01852 function haveCacheVaryCookies() { 01853 $cookieHeader = $this->getRequest()->getHeader( 'cookie' ); 01854 if ( $cookieHeader === false ) { 01855 return false; 01856 } 01857 $cvCookies = $this->getCacheVaryCookies(); 01858 foreach ( $cvCookies as $cookieName ) { 01859 # Check for a simple string match, like the way squid does it 01860 if ( strpos( $cookieHeader, $cookieName ) !== false ) { 01861 wfDebug( __METHOD__ . ": found $cookieName\n" ); 01862 return true; 01863 } 01864 } 01865 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" ); 01866 return false; 01867 } 01868 01877 public function addVaryHeader( $header, $option = null ) { 01878 if ( !array_key_exists( $header, $this->mVaryHeader ) ) { 01879 $this->mVaryHeader[$header] = (array)$option; 01880 } elseif ( is_array( $option ) ) { 01881 if ( is_array( $this->mVaryHeader[$header] ) ) { 01882 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option ); 01883 } else { 01884 $this->mVaryHeader[$header] = $option; 01885 } 01886 } 01887 $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] ); 01888 } 01889 01896 public function getVaryHeader() { 01897 return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ); 01898 } 01899 01905 public function getXVO() { 01906 $cvCookies = $this->getCacheVaryCookies(); 01907 01908 $cookiesOption = array(); 01909 foreach ( $cvCookies as $cookieName ) { 01910 $cookiesOption[] = 'string-contains=' . $cookieName; 01911 } 01912 $this->addVaryHeader( 'Cookie', $cookiesOption ); 01913 01914 $headers = array(); 01915 foreach ( $this->mVaryHeader as $header => $option ) { 01916 $newheader = $header; 01917 if ( is_array( $option ) && count( $option ) > 0 ) { 01918 $newheader .= ';' . implode( ';', $option ); 01919 } 01920 $headers[] = $newheader; 01921 } 01922 $xvo = 'X-Vary-Options: ' . implode( ',', $headers ); 01923 01924 return $xvo; 01925 } 01926 01935 function addAcceptLanguage() { 01936 $title = $this->getTitle(); 01937 if ( !$title instanceof Title ) { 01938 return; 01939 } 01940 01941 $lang = $title->getPageLanguage(); 01942 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) { 01943 $variants = $lang->getVariants(); 01944 $aloption = array(); 01945 foreach ( $variants as $variant ) { 01946 if ( $variant === $lang->getCode() ) { 01947 continue; 01948 } else { 01949 $aloption[] = 'string-contains=' . $variant; 01950 01951 // IE and some other browsers use BCP 47 standards in 01952 // their Accept-Language header, like "zh-CN" or "zh-Hant". 01953 // We should handle these too. 01954 $variantBCP47 = wfBCP47( $variant ); 01955 if ( $variantBCP47 !== $variant ) { 01956 $aloption[] = 'string-contains=' . $variantBCP47; 01957 } 01958 } 01959 } 01960 $this->addVaryHeader( 'Accept-Language', $aloption ); 01961 } 01962 } 01963 01974 public function preventClickjacking( $enable = true ) { 01975 $this->mPreventClickjacking = $enable; 01976 } 01977 01983 public function allowClickjacking() { 01984 $this->mPreventClickjacking = false; 01985 } 01986 01993 public function getPreventClickjacking() { 01994 return $this->mPreventClickjacking; 01995 } 01996 02004 public function getFrameOptions() { 02005 $config = $this->getConfig(); 02006 if ( $config->get( 'BreakFrames' ) ) { 02007 return 'DENY'; 02008 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) { 02009 return $config->get( 'EditPageFrameOptions' ); 02010 } 02011 return false; 02012 } 02013 02017 public function sendCacheControl() { 02018 $response = $this->getRequest()->response(); 02019 $config = $this->getConfig(); 02020 if ( $config->get( 'UseETag' ) && $this->mETag ) { 02021 $response->header( "ETag: $this->mETag" ); 02022 } 02023 02024 $this->addVaryHeader( 'Cookie' ); 02025 $this->addAcceptLanguage(); 02026 02027 # don't serve compressed data to clients who can't handle it 02028 # maintain different caches for logged-in users and non-logged in ones 02029 $response->header( $this->getVaryHeader() ); 02030 02031 if ( $config->get( 'UseXVO' ) ) { 02032 # Add an X-Vary-Options header for Squid with Wikimedia patches 02033 $response->header( $this->getXVO() ); 02034 } 02035 02036 if ( $this->mEnableClientCache ) { 02037 if ( 02038 $config->get( 'UseSquid' ) && session_id() == '' && !$this->isPrintable() && 02039 $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() 02040 ) { 02041 if ( $config->get( 'UseESI' ) ) { 02042 # We'll purge the proxy cache explicitly, but require end user agents 02043 # to revalidate against the proxy on each visit. 02044 # Surrogate-Control controls our Squid, Cache-Control downstream caches 02045 wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", 'log' ); 02046 # start with a shorter timeout for initial testing 02047 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"'); 02048 $response->header( 'Surrogate-Control: max-age=' . $config->get( 'SquidMaxage' ) 02049 . '+' . $this->mSquidMaxage . ', content="ESI/1.0"' ); 02050 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' ); 02051 } else { 02052 # We'll purge the proxy cache for anons explicitly, but require end user agents 02053 # to revalidate against the proxy on each visit. 02054 # IMPORTANT! The Squid needs to replace the Cache-Control header with 02055 # Cache-Control: s-maxage=0, must-revalidate, max-age=0 02056 wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", 'log' ); 02057 # start with a shorter timeout for initial testing 02058 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" ); 02059 $response->header( 'Cache-Control: s-maxage=' . $this->mSquidMaxage 02060 . ', must-revalidate, max-age=0' ); 02061 } 02062 } else { 02063 # We do want clients to cache if they can, but they *must* check for updates 02064 # on revisiting the page. 02065 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", 'log' ); 02066 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 02067 $response->header( "Cache-Control: private, must-revalidate, max-age=0" ); 02068 } 02069 if ( $this->mLastModified ) { 02070 $response->header( "Last-Modified: {$this->mLastModified}" ); 02071 } 02072 } else { 02073 wfDebug( __METHOD__ . ": no caching **\n", 'log' ); 02074 02075 # In general, the absence of a last modified header should be enough to prevent 02076 # the client from using its cache. We send a few other things just to make sure. 02077 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 02078 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); 02079 $response->header( 'Pragma: no-cache' ); 02080 } 02081 } 02082 02087 public function output() { 02088 global $wgLanguageCode; 02089 02090 if ( $this->mDoNothing ) { 02091 return; 02092 } 02093 02094 wfProfileIn( __METHOD__ ); 02095 02096 $response = $this->getRequest()->response(); 02097 $config = $this->getConfig(); 02098 02099 if ( $this->mRedirect != '' ) { 02100 # Standards require redirect URLs to be absolute 02101 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT ); 02102 02103 $redirect = $this->mRedirect; 02104 $code = $this->mRedirectCode; 02105 02106 if ( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) { 02107 if ( $code == '301' || $code == '303' ) { 02108 if ( !$config->get( 'DebugRedirects' ) ) { 02109 $message = HttpStatus::getMessage( $code ); 02110 $response->header( "HTTP/1.1 $code $message" ); 02111 } 02112 $this->mLastModified = wfTimestamp( TS_RFC2822 ); 02113 } 02114 if ( $config->get( 'VaryOnXFP' ) ) { 02115 $this->addVaryHeader( 'X-Forwarded-Proto' ); 02116 } 02117 $this->sendCacheControl(); 02118 02119 $response->header( "Content-Type: text/html; charset=utf-8" ); 02120 if ( $config->get( 'DebugRedirects' ) ) { 02121 $url = htmlspecialchars( $redirect ); 02122 print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n"; 02123 print "<p>Location: <a href=\"$url\">$url</a></p>\n"; 02124 print "</body>\n</html>\n"; 02125 } else { 02126 $response->header( 'Location: ' . $redirect ); 02127 } 02128 } 02129 02130 wfProfileOut( __METHOD__ ); 02131 return; 02132 } elseif ( $this->mStatusCode ) { 02133 $message = HttpStatus::getMessage( $this->mStatusCode ); 02134 if ( $message ) { 02135 $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message ); 02136 } 02137 } 02138 02139 # Buffer output; final headers may depend on later processing 02140 ob_start(); 02141 02142 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' ); 02143 $response->header( 'Content-language: ' . $wgLanguageCode ); 02144 02145 // Avoid Internet Explorer "compatibility view" in IE 8-10, so that 02146 // jQuery etc. can work correctly. 02147 $response->header( 'X-UA-Compatible: IE=Edge' ); 02148 02149 // Prevent framing, if requested 02150 $frameOptions = $this->getFrameOptions(); 02151 if ( $frameOptions ) { 02152 $response->header( "X-Frame-Options: $frameOptions" ); 02153 } 02154 02155 if ( $this->mArticleBodyOnly ) { 02156 echo $this->mBodytext; 02157 } else { 02158 02159 $sk = $this->getSkin(); 02160 // add skin specific modules 02161 $modules = $sk->getDefaultModules(); 02162 02163 // enforce various default modules for all skins 02164 $coreModules = array( 02165 // keep this list as small as possible 02166 'mediawiki.page.startup', 02167 'mediawiki.user', 02168 ); 02169 02170 // Support for high-density display images if enabled 02171 if ( $config->get( 'ResponsiveImages' ) ) { 02172 $coreModules[] = 'mediawiki.hidpi'; 02173 } 02174 02175 $this->addModules( $coreModules ); 02176 foreach ( $modules as $group ) { 02177 $this->addModules( $group ); 02178 } 02179 MWDebug::addModules( $this ); 02180 02181 // Hook that allows last minute changes to the output page, e.g. 02182 // adding of CSS or Javascript by extensions. 02183 wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) ); 02184 02185 wfProfileIn( 'Output-skin' ); 02186 $sk->outputPage(); 02187 wfProfileOut( 'Output-skin' ); 02188 } 02189 02190 // This hook allows last minute changes to final overall output by modifying output buffer 02191 wfRunHooks( 'AfterFinalPageOutput', array( $this ) ); 02192 02193 $this->sendCacheControl(); 02194 02195 ob_end_flush(); 02196 02197 wfProfileOut( __METHOD__ ); 02198 } 02199 02206 public function out( $ins ) { 02207 wfDeprecated( __METHOD__, '1.22' ); 02208 print $ins; 02209 } 02210 02215 function blockedPage() { 02216 throw new UserBlockedError( $this->getUser()->mBlock ); 02217 } 02218 02229 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) { 02230 $this->setPageTitle( $pageTitle ); 02231 if ( $htmlTitle !== false ) { 02232 $this->setHTMLTitle( $htmlTitle ); 02233 } 02234 $this->setRobotPolicy( 'noindex,nofollow' ); 02235 $this->setArticleRelated( false ); 02236 $this->enableClientCache( false ); 02237 $this->mRedirect = ''; 02238 $this->clearSubtitle(); 02239 $this->clearHTML(); 02240 } 02241 02254 public function showErrorPage( $title, $msg, $params = array() ) { 02255 if ( !$title instanceof Message ) { 02256 $title = $this->msg( $title ); 02257 } 02258 02259 $this->prepareErrorPage( $title ); 02260 02261 if ( $msg instanceof Message ) { 02262 if ( $params !== array() ) { 02263 trigger_error( 'Argument ignored: $params. The message parameters argument ' 02264 . 'is discarded when the $msg argument is a Message object instead of ' 02265 . 'a string.', E_USER_NOTICE ); 02266 } 02267 $this->addHTML( $msg->parseAsBlock() ); 02268 } else { 02269 $this->addWikiMsgArray( $msg, $params ); 02270 } 02271 02272 $this->returnToMain(); 02273 } 02274 02281 public function showPermissionsErrorPage( array $errors, $action = null ) { 02282 // For some action (read, edit, create and upload), display a "login to do this action" 02283 // error if all of the following conditions are met: 02284 // 1. the user is not logged in 02285 // 2. the only error is insufficient permissions (i.e. no block or something else) 02286 // 3. the error can be avoided simply by logging in 02287 if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) ) 02288 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] ) 02289 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' ) 02290 && ( User::groupHasPermission( 'user', $action ) 02291 || User::groupHasPermission( 'autoconfirmed', $action ) ) 02292 ) { 02293 $displayReturnto = null; 02294 02295 # Due to bug 32276, if a user does not have read permissions, 02296 # $this->getTitle() will just give Special:Badtitle, which is 02297 # not especially useful as a returnto parameter. Use the title 02298 # from the request instead, if there was one. 02299 $request = $this->getRequest(); 02300 $returnto = Title::newFromURL( $request->getVal( 'title', '' ) ); 02301 if ( $action == 'edit' ) { 02302 $msg = 'whitelistedittext'; 02303 $displayReturnto = $returnto; 02304 } elseif ( $action == 'createpage' || $action == 'createtalk' ) { 02305 $msg = 'nocreatetext'; 02306 } elseif ( $action == 'upload' ) { 02307 $msg = 'uploadnologintext'; 02308 } else { # Read 02309 $msg = 'loginreqpagetext'; 02310 $displayReturnto = Title::newMainPage(); 02311 } 02312 02313 $query = array(); 02314 02315 if ( $returnto ) { 02316 $query['returnto'] = $returnto->getPrefixedText(); 02317 02318 if ( !$request->wasPosted() ) { 02319 $returntoquery = $request->getValues(); 02320 unset( $returntoquery['title'] ); 02321 unset( $returntoquery['returnto'] ); 02322 unset( $returntoquery['returntoquery'] ); 02323 $query['returntoquery'] = wfArrayToCgi( $returntoquery ); 02324 } 02325 } 02326 $loginLink = Linker::linkKnown( 02327 SpecialPage::getTitleFor( 'Userlogin' ), 02328 $this->msg( 'loginreqlink' )->escaped(), 02329 array(), 02330 $query 02331 ); 02332 02333 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) ); 02334 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() ); 02335 02336 # Don't return to a page the user can't read otherwise 02337 # we'll end up in a pointless loop 02338 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) { 02339 $this->returnToMain( null, $displayReturnto ); 02340 } 02341 } else { 02342 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) ); 02343 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) ); 02344 } 02345 } 02346 02353 public function versionRequired( $version ) { 02354 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) ); 02355 02356 $this->addWikiMsg( 'versionrequiredtext', $version ); 02357 $this->returnToMain(); 02358 } 02359 02366 public function permissionRequired( $permission ) { 02367 throw new PermissionsError( $permission ); 02368 } 02369 02375 public function loginToUse() { 02376 throw new PermissionsError( 'read' ); 02377 } 02378 02386 public function formatPermissionsErrorMessage( array $errors, $action = null ) { 02387 if ( $action == null ) { 02388 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n"; 02389 } else { 02390 $action_desc = $this->msg( "action-$action" )->plain(); 02391 $text = $this->msg( 02392 'permissionserrorstext-withaction', 02393 count( $errors ), 02394 $action_desc 02395 )->plain() . "\n\n"; 02396 } 02397 02398 if ( count( $errors ) > 1 ) { 02399 $text .= '<ul class="permissions-errors">' . "\n"; 02400 02401 foreach ( $errors as $error ) { 02402 $text .= '<li>'; 02403 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain(); 02404 $text .= "</li>\n"; 02405 } 02406 $text .= '</ul>'; 02407 } else { 02408 $text .= "<div class=\"permissions-errors\">\n" . 02409 call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() . 02410 "\n</div>"; 02411 } 02412 02413 return $text; 02414 } 02415 02438 public function readOnlyPage( $source = null, $protected = false, 02439 array $reasons = array(), $action = null 02440 ) { 02441 $this->setRobotPolicy( 'noindex,nofollow' ); 02442 $this->setArticleRelated( false ); 02443 02444 // If no reason is given, just supply a default "I can't let you do 02445 // that, Dave" message. Should only occur if called by legacy code. 02446 if ( $protected && empty( $reasons ) ) { 02447 $reasons[] = array( 'badaccess-group0' ); 02448 } 02449 02450 if ( !empty( $reasons ) ) { 02451 // Permissions error 02452 if ( $source ) { 02453 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) ); 02454 $this->addBacklinkSubtitle( $this->getTitle() ); 02455 } else { 02456 $this->setPageTitle( $this->msg( 'badaccess' ) ); 02457 } 02458 $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) ); 02459 } else { 02460 // Wiki is read only 02461 throw new ReadOnlyError; 02462 } 02463 02464 // Show source, if supplied 02465 if ( is_string( $source ) ) { 02466 $this->addWikiMsg( 'viewsourcetext' ); 02467 02468 $pageLang = $this->getTitle()->getPageLanguage(); 02469 $params = array( 02470 'id' => 'wpTextbox1', 02471 'name' => 'wpTextbox1', 02472 'cols' => $this->getUser()->getOption( 'cols' ), 02473 'rows' => $this->getUser()->getOption( 'rows' ), 02474 'readonly' => 'readonly', 02475 'lang' => $pageLang->getHtmlCode(), 02476 'dir' => $pageLang->getDir(), 02477 ); 02478 $this->addHTML( Html::element( 'textarea', $params, $source ) ); 02479 02480 // Show templates used by this article 02481 $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() ); 02482 $this->addHTML( "<div class='templatesUsed'> 02483 $templates 02484 </div> 02485 " ); 02486 } 02487 02488 # If the title doesn't exist, it's fairly pointless to print a return 02489 # link to it. After all, you just tried editing it and couldn't, so 02490 # what's there to do there? 02491 if ( $this->getTitle()->exists() ) { 02492 $this->returnToMain( null, $this->getTitle() ); 02493 } 02494 } 02495 02500 public function rateLimited() { 02501 throw new ThrottledError; 02502 } 02503 02513 public function showLagWarning( $lag ) { 02514 $config = $this->getConfig(); 02515 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) { 02516 $message = $lag < $config->get( 'SlaveLagCritical' ) 02517 ? 'lag-warn-normal' 02518 : 'lag-warn-high'; 02519 $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" ); 02520 $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) ); 02521 } 02522 } 02523 02524 public function showFatalError( $message ) { 02525 $this->prepareErrorPage( $this->msg( 'internalerror' ) ); 02526 02527 $this->addHTML( $message ); 02528 } 02529 02530 public function showUnexpectedValueError( $name, $val ) { 02531 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() ); 02532 } 02533 02534 public function showFileCopyError( $old, $new ) { 02535 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() ); 02536 } 02537 02538 public function showFileRenameError( $old, $new ) { 02539 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() ); 02540 } 02541 02542 public function showFileDeleteError( $name ) { 02543 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() ); 02544 } 02545 02546 public function showFileNotFoundError( $name ) { 02547 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() ); 02548 } 02549 02558 public function addReturnTo( $title, array $query = array(), $text = null, $options = array() ) { 02559 $link = $this->msg( 'returnto' )->rawParams( 02560 Linker::link( $title, $text, array(), $query, $options ) )->escaped(); 02561 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" ); 02562 } 02563 02572 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) { 02573 if ( $returnto == null ) { 02574 $returnto = $this->getRequest()->getText( 'returnto' ); 02575 } 02576 02577 if ( $returntoquery == null ) { 02578 $returntoquery = $this->getRequest()->getText( 'returntoquery' ); 02579 } 02580 02581 if ( $returnto === '' ) { 02582 $returnto = Title::newMainPage(); 02583 } 02584 02585 if ( is_object( $returnto ) ) { 02586 $titleObj = $returnto; 02587 } else { 02588 $titleObj = Title::newFromText( $returnto ); 02589 } 02590 if ( !is_object( $titleObj ) ) { 02591 $titleObj = Title::newMainPage(); 02592 } 02593 02594 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) ); 02595 } 02596 02602 public function headElement( Skin $sk, $includeStyle = true ) { 02603 global $wgContLang; 02604 02605 $userdir = $this->getLanguage()->getDir(); 02606 $sitedir = $wgContLang->getDir(); 02607 02608 $ret = Html::htmlHeader( $sk->getHtmlElementAttributes() ); 02609 02610 if ( $this->getHTMLTitle() == '' ) { 02611 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() ); 02612 } 02613 02614 $openHead = Html::openElement( 'head' ); 02615 if ( $openHead ) { 02616 # Don't bother with the newline if $head == '' 02617 $ret .= "$openHead\n"; 02618 } 02619 02620 if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) { 02621 // Add <meta charset="UTF-8"> 02622 // This should be before <title> since it defines the charset used by 02623 // text including the text inside <title>. 02624 // The spec recommends defining XHTML5's charset using the XML declaration 02625 // instead of meta. 02626 // Our XML declaration is output by Html::htmlHeader. 02627 // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type 02628 // http://www.whatwg.org/html/semantics.html#charset 02629 $ret .= Html::element( 'meta', array( 'charset' => 'UTF-8' ) ) . "\n"; 02630 } 02631 02632 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n"; 02633 02634 foreach ( $this->getHeadLinksArray() as $item ) { 02635 $ret .= $item . "\n"; 02636 } 02637 02638 // No newline after buildCssLinks since makeResourceLoaderLink did that already 02639 $ret .= $this->buildCssLinks(); 02640 02641 $ret .= $this->getHeadScripts() . "\n"; 02642 02643 foreach ( $this->mHeadItems as $item ) { 02644 $ret .= $item . "\n"; 02645 } 02646 02647 $closeHead = Html::closeElement( 'head' ); 02648 if ( $closeHead ) { 02649 $ret .= "$closeHead\n"; 02650 } 02651 02652 $bodyClasses = array(); 02653 $bodyClasses[] = 'mediawiki'; 02654 02655 # Classes for LTR/RTL directionality support 02656 $bodyClasses[] = $userdir; 02657 $bodyClasses[] = "sitedir-$sitedir"; 02658 02659 if ( $this->getLanguage()->capitalizeAllNouns() ) { 02660 # A <body> class is probably not the best way to do this . . . 02661 $bodyClasses[] = 'capitalize-all-nouns'; 02662 } 02663 02664 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() ); 02665 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() ); 02666 $bodyClasses[] = 02667 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) ); 02668 02669 $bodyAttrs = array(); 02670 // While the implode() is not strictly needed, it's used for backwards compatibility 02671 // (this used to be built as a string and hooks likely still expect that). 02672 $bodyAttrs['class'] = implode( ' ', $bodyClasses ); 02673 02674 // Allow skins and extensions to add body attributes they need 02675 $sk->addToBodyAttributes( $this, $bodyAttrs ); 02676 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) ); 02677 02678 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n"; 02679 02680 return $ret; 02681 } 02682 02688 public function getResourceLoader() { 02689 if ( is_null( $this->mResourceLoader ) ) { 02690 $this->mResourceLoader = new ResourceLoader( $this->getConfig() ); 02691 } 02692 return $this->mResourceLoader; 02693 } 02694 02706 protected function makeResourceLoaderLink( $modules, $only, $useESI = false, 02707 array $extraQuery = array(), $loadCall = false 02708 ) { 02709 $modules = (array)$modules; 02710 02711 $links = array( 02712 'html' => '', 02713 'states' => array(), 02714 ); 02715 02716 if ( !count( $modules ) ) { 02717 return $links; 02718 } 02719 02720 if ( count( $modules ) > 1 ) { 02721 // Remove duplicate module requests 02722 $modules = array_unique( $modules ); 02723 // Sort module names so requests are more uniform 02724 sort( $modules ); 02725 02726 if ( ResourceLoader::inDebugMode() ) { 02727 // Recursively call us for every item 02728 foreach ( $modules as $name ) { 02729 $link = $this->makeResourceLoaderLink( $name, $only, $useESI ); 02730 $links['html'] .= $link['html']; 02731 $links['states'] += $link['states']; 02732 } 02733 return $links; 02734 } 02735 } 02736 02737 if ( !is_null( $this->mTarget ) ) { 02738 $extraQuery['target'] = $this->mTarget; 02739 } 02740 02741 // Create keyed-by-source and then keyed-by-group list of module objects from modules list 02742 $sortedModules = array(); 02743 $resourceLoader = $this->getResourceLoader(); 02744 $resourceLoaderUseESI = $this->getConfig()->get( 'ResourceLoaderUseESI' ); 02745 foreach ( $modules as $name ) { 02746 $module = $resourceLoader->getModule( $name ); 02747 # Check that we're allowed to include this module on this page 02748 if ( !$module 02749 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) 02750 && $only == ResourceLoaderModule::TYPE_SCRIPTS ) 02751 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES ) 02752 && $only == ResourceLoaderModule::TYPE_STYLES ) 02753 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED ) 02754 && $only == ResourceLoaderModule::TYPE_COMBINED ) 02755 || ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) 02756 ) { 02757 continue; 02758 } 02759 02760 $sortedModules[$module->getSource()][$module->getGroup()][$name] = $module; 02761 } 02762 02763 foreach ( $sortedModules as $source => $groups ) { 02764 foreach ( $groups as $group => $grpModules ) { 02765 // Special handling for user-specific groups 02766 $user = null; 02767 if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) { 02768 $user = $this->getUser()->getName(); 02769 } 02770 02771 // Create a fake request based on the one we are about to make so modules return 02772 // correct timestamp and emptiness data 02773 $query = ResourceLoader::makeLoaderQuery( 02774 array(), // modules; not determined yet 02775 $this->getLanguage()->getCode(), 02776 $this->getSkin()->getSkinName(), 02777 $user, 02778 null, // version; not determined yet 02779 ResourceLoader::inDebugMode(), 02780 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only, 02781 $this->isPrintable(), 02782 $this->getRequest()->getBool( 'handheld' ), 02783 $extraQuery 02784 ); 02785 $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); 02786 02787 02788 // Extract modules that know they're empty and see if we have one or more 02789 // raw modules 02790 $isRaw = false; 02791 foreach ( $grpModules as $key => $module ) { 02792 // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857) 02793 // If we're only getting the styles, we don't need to do anything for empty modules. 02794 if ( $module->isKnownEmpty( $context ) ) { 02795 unset( $grpModules[$key] ); 02796 if ( $only !== ResourceLoaderModule::TYPE_STYLES ) { 02797 $links['states'][$key] = 'ready'; 02798 } 02799 } 02800 02801 $isRaw |= $module->isRaw(); 02802 } 02803 02804 // If there are no non-empty modules, skip this group 02805 if ( count( $grpModules ) === 0 ) { 02806 continue; 02807 } 02808 02809 // Inline private modules. These can't be loaded through load.php for security 02810 // reasons, see bug 34907. Note that these modules should be loaded from 02811 // getHeadScripts() before the first loader call. Otherwise other modules can't 02812 // properly use them as dependencies (bug 30914) 02813 if ( $group === 'private' ) { 02814 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02815 $links['html'] .= Html::inlineStyle( 02816 $resourceLoader->makeModuleResponse( $context, $grpModules ) 02817 ); 02818 } else { 02819 $links['html'] .= Html::inlineScript( 02820 ResourceLoader::makeLoaderConditionalScript( 02821 $resourceLoader->makeModuleResponse( $context, $grpModules ) 02822 ) 02823 ); 02824 } 02825 $links['html'] .= "\n"; 02826 continue; 02827 } 02828 02829 // Special handling for the user group; because users might change their stuff 02830 // on-wiki like user pages, or user preferences; we need to find the highest 02831 // timestamp of these user-changeable modules so we can ensure cache misses on change 02832 // This should NOT be done for the site group (bug 27564) because anons get that too 02833 // and we shouldn't be putting timestamps in Squid-cached HTML 02834 $version = null; 02835 if ( $group === 'user' ) { 02836 // Get the maximum timestamp 02837 $timestamp = 1; 02838 foreach ( $grpModules as $module ) { 02839 $timestamp = max( $timestamp, $module->getModifiedTime( $context ) ); 02840 } 02841 // Add a version parameter so cache will break when things change 02842 $query['version'] = wfTimestamp( TS_ISO_8601_BASIC, $timestamp ); 02843 } 02844 02845 $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $grpModules ) ); 02846 $moduleContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) ); 02847 $url = $resourceLoader->createLoaderURL( $source, $moduleContext, $extraQuery ); 02848 02849 if ( $useESI && $resourceLoaderUseESI ) { 02850 $esi = Xml::element( 'esi:include', array( 'src' => $url ) ); 02851 if ( $only == ResourceLoaderModule::TYPE_STYLES ) { 02852 $link = Html::inlineStyle( $esi ); 02853 } else { 02854 $link = Html::inlineScript( $esi ); 02855 } 02856 } else { 02857 // Automatically select style/script elements 02858 if ( $only === ResourceLoaderModule::TYPE_STYLES ) { 02859 $link = Html::linkedStyle( $url ); 02860 } elseif ( $loadCall ) { 02861 $link = Html::inlineScript( 02862 ResourceLoader::makeLoaderConditionalScript( 02863 Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) ) 02864 ) 02865 ); 02866 } else { 02867 $link = Html::linkedScript( $url ); 02868 if ( $context->getOnly() === 'scripts' && !$context->getRaw() && !$isRaw ) { 02869 // Wrap only=script requests in a conditional as browsers not supported 02870 // by the startup module would unconditionally execute this module. 02871 // Otherwise users will get "ReferenceError: mw is undefined" or 02872 // "jQuery is undefined" from e.g. a "site" module. 02873 $link = Html::inlineScript( 02874 ResourceLoader::makeLoaderConditionalScript( 02875 Xml::encodeJsCall( 'document.write', array( $link ) ) 02876 ) 02877 ); 02878 } 02879 02880 // For modules requested directly in the html via <link> or <script>, 02881 // tell mw.loader they are being loading to prevent duplicate requests. 02882 foreach ( $grpModules as $key => $module ) { 02883 // Don't output state=loading for the startup module.. 02884 if ( $key !== 'startup' ) { 02885 $links['states'][$key] = 'loading'; 02886 } 02887 } 02888 } 02889 } 02890 02891 if ( $group == 'noscript' ) { 02892 $links['html'] .= Html::rawElement( 'noscript', array(), $link ) . "\n"; 02893 } else { 02894 $links['html'] .= $link . "\n"; 02895 } 02896 } 02897 } 02898 02899 return $links; 02900 } 02901 02907 protected static function getHtmlFromLoaderLinks( array $links ) { 02908 $html = ''; 02909 $states = array(); 02910 foreach ( $links as $link ) { 02911 if ( !is_array( $link ) ) { 02912 $html .= $link; 02913 } else { 02914 $html .= $link['html']; 02915 $states += $link['states']; 02916 } 02917 } 02918 02919 if ( count( $states ) ) { 02920 $html = Html::inlineScript( 02921 ResourceLoader::makeLoaderConditionalScript( 02922 ResourceLoader::makeLoaderStateScript( $states ) 02923 ) 02924 ) . "\n" . $html; 02925 } 02926 02927 return $html; 02928 } 02929 02936 function getHeadScripts() { 02937 // Startup - this will immediately load jquery and mediawiki modules 02938 $links = array(); 02939 $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true ); 02940 02941 // Load config before anything else 02942 $links[] = Html::inlineScript( 02943 ResourceLoader::makeLoaderConditionalScript( 02944 ResourceLoader::makeConfigSetScript( $this->getJSVars() ) 02945 ) 02946 ); 02947 02948 // Load embeddable private modules before any loader links 02949 // This needs to be TYPE_COMBINED so these modules are properly wrapped 02950 // in mw.loader.implement() calls and deferred until mw.user is available 02951 $embedScripts = array( 'user.options', 'user.tokens' ); 02952 $links[] = $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED ); 02953 02954 // Scripts and messages "only" requests marked for top inclusion 02955 // Messages should go first 02956 $links[] = $this->makeResourceLoaderLink( 02957 $this->getModuleMessages( true, 'top' ), 02958 ResourceLoaderModule::TYPE_MESSAGES 02959 ); 02960 $links[] = $this->makeResourceLoaderLink( 02961 $this->getModuleScripts( true, 'top' ), 02962 ResourceLoaderModule::TYPE_SCRIPTS 02963 ); 02964 02965 // Modules requests - let the client calculate dependencies and batch requests as it likes 02966 // Only load modules that have marked themselves for loading at the top 02967 $modules = $this->getModules( true, 'top' ); 02968 if ( $modules ) { 02969 $links[] = Html::inlineScript( 02970 ResourceLoader::makeLoaderConditionalScript( 02971 Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) 02972 ) 02973 ); 02974 } 02975 02976 if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) { 02977 $links[] = $this->getScriptsForBottomQueue( true ); 02978 } 02979 02980 return self::getHtmlFromLoaderLinks( $links ); 02981 } 02982 02994 function getScriptsForBottomQueue( $inHead ) { 02995 // Scripts and messages "only" requests marked for bottom inclusion 02996 // If we're in the <head>, use load() calls rather than <script src="..."> tags 02997 // Messages should go first 02998 $links = array(); 02999 $links[] = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), 03000 ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(), 03001 /* $loadCall = */ $inHead 03002 ); 03003 $links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), 03004 ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(), 03005 /* $loadCall = */ $inHead 03006 ); 03007 03008 // Modules requests - let the client calculate dependencies and batch requests as it likes 03009 // Only load modules that have marked themselves for loading at the bottom 03010 $modules = $this->getModules( true, 'bottom' ); 03011 if ( $modules ) { 03012 $links[] = Html::inlineScript( 03013 ResourceLoader::makeLoaderConditionalScript( 03014 Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) ) 03015 ) 03016 ); 03017 } 03018 03019 // Legacy Scripts 03020 $links[] = "\n" . $this->mScripts; 03021 03022 // Add site JS if enabled 03023 $links[] = $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS, 03024 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 03025 ); 03026 03027 // Add user JS if enabled 03028 if ( $this->getConfig()->get( 'AllowUserJs' ) 03029 && $this->getUser()->isLoggedIn() 03030 && $this->getTitle() 03031 && $this->getTitle()->isJsSubpage() 03032 && $this->userCanPreview() 03033 ) { 03034 # XXX: additional security check/prompt? 03035 // We're on a preview of a JS subpage 03036 // Exclude this page from the user module in case it's in there (bug 26283) 03037 $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false, 03038 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead 03039 ); 03040 // Load the previewed JS 03041 $links[] = Html::inlineScript( "\n" 03042 . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n"; 03043 03044 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded 03045 // asynchronously and may arrive *after* the inline script here. So the previewed code 03046 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js... 03047 } else { 03048 // Include the user module normally, i.e., raw to avoid it being wrapped in a closure. 03049 $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, 03050 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 03051 ); 03052 } 03053 03054 // Group JS is only enabled if site JS is enabled. 03055 $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED, 03056 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead 03057 ); 03058 03059 return self::getHtmlFromLoaderLinks( $links ); 03060 } 03061 03066 function getBottomScripts() { 03067 // Optimise jQuery ready event cross-browser. 03068 // This also enforces $.isReady to be true at </body> which fixes the 03069 // mw.loader bug in Firefox with using document.write between </body> 03070 // and the DOMContentReady event (bug 47457). 03071 $html = Html::inlineScript( 'window.jQuery && jQuery.ready();' ); 03072 03073 if ( !$this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) { 03074 $html .= $this->getScriptsForBottomQueue( false ); 03075 } 03076 03077 return $html; 03078 } 03079 03086 public function getJsConfigVars() { 03087 return $this->mJsConfigVars; 03088 } 03089 03096 public function addJsConfigVars( $keys, $value = null ) { 03097 if ( is_array( $keys ) ) { 03098 foreach ( $keys as $key => $value ) { 03099 $this->mJsConfigVars[$key] = $value; 03100 } 03101 return; 03102 } 03103 03104 $this->mJsConfigVars[$keys] = $value; 03105 } 03106 03116 private function getJSVars() { 03117 global $wgContLang; 03118 03119 $curRevisionId = 0; 03120 $articleId = 0; 03121 $canonicalSpecialPageName = false; # bug 21115 03122 03123 $title = $this->getTitle(); 03124 $ns = $title->getNamespace(); 03125 $canonicalNamespace = MWNamespace::exists( $ns ) 03126 ? MWNamespace::getCanonicalName( $ns ) 03127 : $title->getNsText(); 03128 03129 $sk = $this->getSkin(); 03130 // Get the relevant title so that AJAX features can use the correct page name 03131 // when making API requests from certain special pages (bug 34972). 03132 $relevantTitle = $sk->getRelevantTitle(); 03133 $relevantUser = $sk->getRelevantUser(); 03134 03135 if ( $ns == NS_SPECIAL ) { 03136 list( $canonicalSpecialPageName, /*...*/ ) = 03137 SpecialPageFactory::resolveAlias( $title->getDBkey() ); 03138 } elseif ( $this->canUseWikiPage() ) { 03139 $wikiPage = $this->getWikiPage(); 03140 $curRevisionId = $wikiPage->getLatest(); 03141 $articleId = $wikiPage->getId(); 03142 } 03143 03144 $lang = $title->getPageLanguage(); 03145 03146 // Pre-process information 03147 $separatorTransTable = $lang->separatorTransformTable(); 03148 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array(); 03149 $compactSeparatorTransTable = array( 03150 implode( "\t", array_keys( $separatorTransTable ) ), 03151 implode( "\t", $separatorTransTable ), 03152 ); 03153 $digitTransTable = $lang->digitTransformTable(); 03154 $digitTransTable = $digitTransTable ? $digitTransTable : array(); 03155 $compactDigitTransTable = array( 03156 implode( "\t", array_keys( $digitTransTable ) ), 03157 implode( "\t", $digitTransTable ), 03158 ); 03159 03160 $user = $this->getUser(); 03161 03162 $vars = array( 03163 'wgCanonicalNamespace' => $canonicalNamespace, 03164 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName, 03165 'wgNamespaceNumber' => $title->getNamespace(), 03166 'wgPageName' => $title->getPrefixedDBkey(), 03167 'wgTitle' => $title->getText(), 03168 'wgCurRevisionId' => $curRevisionId, 03169 'wgRevisionId' => (int)$this->getRevisionId(), 03170 'wgArticleId' => $articleId, 03171 'wgIsArticle' => $this->isArticle(), 03172 'wgIsRedirect' => $title->isRedirect(), 03173 'wgAction' => Action::getActionName( $this->getContext() ), 03174 'wgUserName' => $user->isAnon() ? null : $user->getName(), 03175 'wgUserGroups' => $user->getEffectiveGroups(), 03176 'wgCategories' => $this->getCategories(), 03177 'wgBreakFrames' => $this->getFrameOptions() == 'DENY', 03178 'wgPageContentLanguage' => $lang->getCode(), 03179 'wgPageContentModel' => $title->getContentModel(), 03180 'wgSeparatorTransformTable' => $compactSeparatorTransTable, 03181 'wgDigitTransformTable' => $compactDigitTransTable, 03182 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(), 03183 'wgMonthNames' => $lang->getMonthNamesArray(), 03184 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(), 03185 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(), 03186 ); 03187 03188 if ( $user->isLoggedIn() ) { 03189 $vars['wgUserId'] = $user->getId(); 03190 $vars['wgUserEditCount'] = $user->getEditCount(); 03191 $userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() ); 03192 $vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null; 03193 // Get the revision ID of the oldest new message on the user's talk 03194 // page. This can be used for constructing new message alerts on 03195 // the client side. 03196 $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId(); 03197 } 03198 03199 if ( $wgContLang->hasVariants() ) { 03200 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant(); 03201 } 03202 // Same test as SkinTemplate 03203 $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user ) 03204 && ( $title->exists() || $title->quickUserCan( 'create', $user ) ); 03205 03206 foreach ( $title->getRestrictionTypes() as $type ) { 03207 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type ); 03208 } 03209 03210 if ( $title->isMainPage() ) { 03211 $vars['wgIsMainPage'] = true; 03212 } 03213 03214 if ( $this->mRedirectedFrom ) { 03215 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey(); 03216 } 03217 03218 if ( $relevantUser ) { 03219 $vars['wgRelevantUserName'] = $relevantUser->getName(); 03220 } 03221 03222 // Allow extensions to add their custom variables to the mw.config map. 03223 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not 03224 // page-dependant but site-wide (without state). 03225 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead. 03226 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) ); 03227 03228 // Merge in variables from addJsConfigVars last 03229 return array_merge( $vars, $this->getJsConfigVars() ); 03230 } 03231 03241 public function userCanPreview() { 03242 if ( $this->getRequest()->getVal( 'action' ) != 'submit' 03243 || !$this->getRequest()->wasPosted() 03244 || !$this->getUser()->matchEditToken( 03245 $this->getRequest()->getVal( 'wpEditToken' ) ) 03246 ) { 03247 return false; 03248 } 03249 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) { 03250 return false; 03251 } 03252 03253 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) ); 03254 } 03255 03259 public function getHeadLinksArray() { 03260 global $wgVersion; 03261 03262 $tags = array(); 03263 $config = $this->getConfig(); 03264 03265 $canonicalUrl = $this->mCanonicalUrl; 03266 03267 $tags['meta-generator'] = Html::element( 'meta', array( 03268 'name' => 'generator', 03269 'content' => "MediaWiki $wgVersion", 03270 ) ); 03271 03272 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}"; 03273 if ( $p !== 'index,follow' ) { 03274 // http://www.robotstxt.org/wc/meta-user.html 03275 // Only show if it's different from the default robots policy 03276 $tags['meta-robots'] = Html::element( 'meta', array( 03277 'name' => 'robots', 03278 'content' => $p, 03279 ) ); 03280 } 03281 03282 foreach ( $this->mMetatags as $tag ) { 03283 if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) { 03284 $a = 'http-equiv'; 03285 $tag[0] = substr( $tag[0], 5 ); 03286 } else { 03287 $a = 'name'; 03288 } 03289 $tagName = "meta-{$tag[0]}"; 03290 if ( isset( $tags[$tagName] ) ) { 03291 $tagName .= $tag[1]; 03292 } 03293 $tags[$tagName] = Html::element( 'meta', 03294 array( 03295 $a => $tag[0], 03296 'content' => $tag[1] 03297 ) 03298 ); 03299 } 03300 03301 foreach ( $this->mLinktags as $tag ) { 03302 $tags[] = Html::element( 'link', $tag ); 03303 } 03304 03305 # Universal edit button 03306 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) { 03307 $user = $this->getUser(); 03308 if ( $this->getTitle()->quickUserCan( 'edit', $user ) 03309 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) { 03310 // Original UniversalEditButton 03311 $msg = $this->msg( 'edit' )->text(); 03312 $tags['universal-edit-button'] = Html::element( 'link', array( 03313 'rel' => 'alternate', 03314 'type' => 'application/x-wiki', 03315 'title' => $msg, 03316 'href' => $this->getTitle()->getEditURL(), 03317 ) ); 03318 // Alternate edit link 03319 $tags['alternative-edit'] = Html::element( 'link', array( 03320 'rel' => 'edit', 03321 'title' => $msg, 03322 'href' => $this->getTitle()->getEditURL(), 03323 ) ); 03324 } 03325 } 03326 03327 # Generally the order of the favicon and apple-touch-icon links 03328 # should not matter, but Konqueror (3.5.9 at least) incorrectly 03329 # uses whichever one appears later in the HTML source. Make sure 03330 # apple-touch-icon is specified first to avoid this. 03331 if ( $config->get( 'AppleTouchIcon' ) !== false ) { 03332 $tags['apple-touch-icon'] = Html::element( 'link', array( 03333 'rel' => 'apple-touch-icon', 03334 'href' => $config->get( 'AppleTouchIcon' ) 03335 ) ); 03336 } 03337 03338 if ( $config->get( 'Favicon' ) !== false ) { 03339 $tags['favicon'] = Html::element( 'link', array( 03340 'rel' => 'shortcut icon', 03341 'href' => $config->get( 'Favicon' ) 03342 ) ); 03343 } 03344 03345 # OpenSearch description link 03346 $tags['opensearch'] = Html::element( 'link', array( 03347 'rel' => 'search', 03348 'type' => 'application/opensearchdescription+xml', 03349 'href' => wfScript( 'opensearch_desc' ), 03350 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(), 03351 ) ); 03352 03353 if ( $config->get( 'EnableAPI' ) ) { 03354 # Real Simple Discovery link, provides auto-discovery information 03355 # for the MediaWiki API (and potentially additional custom API 03356 # support such as WordPress or Twitter-compatible APIs for a 03357 # blogging extension, etc) 03358 $tags['rsd'] = Html::element( 'link', array( 03359 'rel' => 'EditURI', 03360 'type' => 'application/rsd+xml', 03361 // Output a protocol-relative URL here if $wgServer is protocol-relative 03362 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though 03363 'href' => wfExpandUrl( wfAppendQuery( 03364 wfScript( 'api' ), 03365 array( 'action' => 'rsd' ) ), 03366 PROTO_RELATIVE 03367 ), 03368 ) ); 03369 } 03370 03371 # Language variants 03372 if ( !$config->get( 'DisableLangConversion' ) ) { 03373 $lang = $this->getTitle()->getPageLanguage(); 03374 if ( $lang->hasVariants() ) { 03375 $variants = $lang->getVariants(); 03376 foreach ( $variants as $_v ) { 03377 $tags["variant-$_v"] = Html::element( 'link', array( 03378 'rel' => 'alternate', 03379 'hreflang' => wfBCP47( $_v ), 03380 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) ) 03381 ); 03382 } 03383 } 03384 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en 03385 $tags["variant-x-default"] = Html::element( 'link', array( 03386 'rel' => 'alternate', 03387 'hreflang' => 'x-default', 03388 'href' => $this->getTitle()->getLocalURL() ) ); 03389 } 03390 03391 # Copyright 03392 $copyright = ''; 03393 if ( $config->get( 'RightsPage' ) ) { 03394 $copy = Title::newFromText( $config->get( 'RightsPage' ) ); 03395 03396 if ( $copy ) { 03397 $copyright = $copy->getLocalURL(); 03398 } 03399 } 03400 03401 if ( !$copyright && $config->get( 'RightsUrl' ) ) { 03402 $copyright = $config->get( 'RightsUrl' ); 03403 } 03404 03405 if ( $copyright ) { 03406 $tags['copyright'] = Html::element( 'link', array( 03407 'rel' => 'copyright', 03408 'href' => $copyright ) 03409 ); 03410 } 03411 03412 # Feeds 03413 if ( $config->get( 'Feed' ) ) { 03414 foreach ( $this->getSyndicationLinks() as $format => $link ) { 03415 # Use the page name for the title. In principle, this could 03416 # lead to issues with having the same name for different feeds 03417 # corresponding to the same page, but we can't avoid that at 03418 # this low a level. 03419 03420 $tags[] = $this->feedLink( 03421 $format, 03422 $link, 03423 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep) 03424 $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text() 03425 ); 03426 } 03427 03428 # Recent changes feed should appear on every page (except recentchanges, 03429 # that would be redundant). Put it after the per-page feed to avoid 03430 # changing existing behavior. It's still available, probably via a 03431 # menu in your browser. Some sites might have a different feed they'd 03432 # like to promote instead of the RC feed (maybe like a "Recent New Articles" 03433 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined. 03434 # If so, use it instead. 03435 $sitename = $config->get( 'Sitename' ); 03436 if ( $config->get( 'OverrideSiteFeed' ) ) { 03437 foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) { 03438 // Note, this->feedLink escapes the url. 03439 $tags[] = $this->feedLink( 03440 $type, 03441 $feedUrl, 03442 $this->msg( "site-{$type}-feed", $sitename )->text() 03443 ); 03444 } 03445 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) { 03446 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' ); 03447 foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) { 03448 $tags[] = $this->feedLink( 03449 $format, 03450 $rctitle->getLocalURL( array( 'feed' => $format ) ), 03451 # For grep: 'site-rss-feed', 'site-atom-feed' 03452 $this->msg( "site-{$format}-feed", $sitename )->text() 03453 ); 03454 } 03455 } 03456 } 03457 03458 # Canonical URL 03459 if ( $config->get( 'EnableCanonicalServerLink' ) ) { 03460 if ( $canonicalUrl !== false ) { 03461 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL ); 03462 } else { 03463 $reqUrl = $this->getRequest()->getRequestURL(); 03464 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL ); 03465 } 03466 } 03467 if ( $canonicalUrl !== false ) { 03468 $tags[] = Html::element( 'link', array( 03469 'rel' => 'canonical', 03470 'href' => $canonicalUrl 03471 ) ); 03472 } 03473 03474 return $tags; 03475 } 03476 03482 public function getHeadLinks() { 03483 wfDeprecated( __METHOD__, '1.24' ); 03484 return implode( "\n", $this->getHeadLinksArray() ); 03485 } 03486 03495 private function feedLink( $type, $url, $text ) { 03496 return Html::element( 'link', array( 03497 'rel' => 'alternate', 03498 'type' => "application/$type+xml", 03499 'title' => $text, 03500 'href' => $url ) 03501 ); 03502 } 03503 03513 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) { 03514 $options = array(); 03515 // Even though we expect the media type to be lowercase, but here we 03516 // force it to lowercase to be safe. 03517 if ( $media ) { 03518 $options['media'] = $media; 03519 } 03520 if ( $condition ) { 03521 $options['condition'] = $condition; 03522 } 03523 if ( $dir ) { 03524 $options['dir'] = $dir; 03525 } 03526 $this->styles[$style] = $options; 03527 } 03528 03534 public function addInlineStyle( $style_css, $flip = 'noflip' ) { 03535 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) { 03536 # If wanted, and the interface is right-to-left, flip the CSS 03537 $style_css = CSSJanus::transform( $style_css, true, false ); 03538 } 03539 $this->mInlineStyles .= Html::inlineStyle( $style_css ) . "\n"; 03540 } 03541 03548 public function buildCssLinks() { 03549 global $wgContLang; 03550 03551 $this->getSkin()->setupSkinUserCss( $this ); 03552 03553 // Add ResourceLoader styles 03554 // Split the styles into these groups 03555 $styles = array( 03556 'other' => array(), 03557 'user' => array(), 03558 'site' => array(), 03559 'private' => array(), 03560 'noscript' => array() 03561 ); 03562 $links = array(); 03563 $otherTags = ''; // Tags to append after the normal <link> tags 03564 $resourceLoader = $this->getResourceLoader(); 03565 03566 $moduleStyles = $this->getModuleStyles(); 03567 03568 // Per-site custom styles 03569 $moduleStyles[] = 'site'; 03570 $moduleStyles[] = 'noscript'; 03571 $moduleStyles[] = 'user.groups'; 03572 03573 // Per-user custom styles 03574 if ( $this->getConfig()->get( 'AllowUserCss' ) && $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) { 03575 // We're on a preview of a CSS subpage 03576 // Exclude this page from the user module in case it's in there (bug 26283) 03577 $link = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false, 03578 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ) 03579 ); 03580 $otherTags .= $link['html']; 03581 03582 // Load the previewed CSS 03583 // If needed, Janus it first. This is user-supplied CSS, so it's 03584 // assumed to be right for the content language directionality. 03585 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' ); 03586 if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) { 03587 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false ); 03588 } 03589 $otherTags .= Html::inlineStyle( $previewedCSS ) . "\n"; 03590 } else { 03591 // Load the user styles normally 03592 $moduleStyles[] = 'user'; 03593 } 03594 03595 // Per-user preference styles 03596 $moduleStyles[] = 'user.cssprefs'; 03597 03598 foreach ( $moduleStyles as $name ) { 03599 $module = $resourceLoader->getModule( $name ); 03600 if ( !$module ) { 03601 continue; 03602 } 03603 $group = $module->getGroup(); 03604 // Modules in groups different than the ones listed on top (see $styles assignment) 03605 // will be placed in the "other" group 03606 $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name; 03607 } 03608 03609 // We want site, private and user styles to override dynamically added 03610 // styles from modules, but we want dynamically added styles to override 03611 // statically added styles from other modules. So the order has to be 03612 // other, dynamic, site, private, user. Add statically added styles for 03613 // other modules 03614 $links[] = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES ); 03615 // Add normal styles added through addStyle()/addInlineStyle() here 03616 $links[] = implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles; 03617 // Add marker tag to mark the place where the client-side loader should inject dynamic styles 03618 // We use a <meta> tag with a made-up name for this because that's valid HTML 03619 $links[] = Html::element( 03620 'meta', 03621 array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) 03622 ) . "\n"; 03623 03624 // Add site, private and user styles 03625 // 'private' at present only contains user.options, so put that before 'user' 03626 // Any future private modules will likely have a similar user-specific character 03627 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) { 03628 $links[] = $this->makeResourceLoaderLink( $styles[$group], 03629 ResourceLoaderModule::TYPE_STYLES 03630 ); 03631 } 03632 03633 // Add stuff in $otherTags (previewed user CSS if applicable) 03634 return self::getHtmlFromLoaderLinks( $links ) . $otherTags; 03635 } 03636 03640 public function buildCssLinksArray() { 03641 $links = array(); 03642 03643 // Add any extension CSS 03644 foreach ( $this->mExtStyles as $url ) { 03645 $this->addStyle( $url ); 03646 } 03647 $this->mExtStyles = array(); 03648 03649 foreach ( $this->styles as $file => $options ) { 03650 $link = $this->styleLink( $file, $options ); 03651 if ( $link ) { 03652 $links[$file] = $link; 03653 } 03654 } 03655 return $links; 03656 } 03657 03665 protected function styleLink( $style, array $options ) { 03666 if ( isset( $options['dir'] ) ) { 03667 if ( $this->getLanguage()->getDir() != $options['dir'] ) { 03668 return ''; 03669 } 03670 } 03671 03672 if ( isset( $options['media'] ) ) { 03673 $media = self::transformCssMedia( $options['media'] ); 03674 if ( is_null( $media ) ) { 03675 return ''; 03676 } 03677 } else { 03678 $media = 'all'; 03679 } 03680 03681 if ( substr( $style, 0, 1 ) == '/' || 03682 substr( $style, 0, 5 ) == 'http:' || 03683 substr( $style, 0, 6 ) == 'https:' ) { 03684 $url = $style; 03685 } else { 03686 $config = $this->getConfig(); 03687 $url = $config->get( 'StylePath' ) . '/' . $style . '?' . $config->get( 'StyleVersion' ); 03688 } 03689 03690 $link = Html::linkedStyle( $url, $media ); 03691 03692 if ( isset( $options['condition'] ) ) { 03693 $condition = htmlspecialchars( $options['condition'] ); 03694 $link = "<!--[if $condition]>$link<![endif]-->"; 03695 } 03696 return $link; 03697 } 03698 03706 public static function transformCssMedia( $media ) { 03707 global $wgRequest; 03708 03709 // http://www.w3.org/TR/css3-mediaqueries/#syntax 03710 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i'; 03711 03712 // Switch in on-screen display for media testing 03713 $switches = array( 03714 'printable' => 'print', 03715 'handheld' => 'handheld', 03716 ); 03717 foreach ( $switches as $switch => $targetMedia ) { 03718 if ( $wgRequest->getBool( $switch ) ) { 03719 if ( $media == $targetMedia ) { 03720 $media = ''; 03721 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) { 03722 // This regex will not attempt to understand a comma-separated media_query_list 03723 // 03724 // Example supported values for $media: 03725 // 'screen', 'only screen', 'screen and (min-width: 982px)' ), 03726 // Example NOT supported value for $media: 03727 // '3d-glasses, screen, print and resolution > 90dpi' 03728 // 03729 // If it's a print request, we never want any kind of screen stylesheets 03730 // If it's a handheld request (currently the only other choice with a switch), 03731 // we don't want simple 'screen' but we might want screen queries that 03732 // have a max-width or something, so we'll pass all others on and let the 03733 // client do the query. 03734 if ( $targetMedia == 'print' || $media == 'screen' ) { 03735 return null; 03736 } 03737 } 03738 } 03739 } 03740 03741 return $media; 03742 } 03743 03750 public function addWikiMsg( /*...*/ ) { 03751 $args = func_get_args(); 03752 $name = array_shift( $args ); 03753 $this->addWikiMsgArray( $name, $args ); 03754 } 03755 03764 public function addWikiMsgArray( $name, $args ) { 03765 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() ); 03766 } 03767 03792 public function wrapWikiMsg( $wrap /*, ...*/ ) { 03793 $msgSpecs = func_get_args(); 03794 array_shift( $msgSpecs ); 03795 $msgSpecs = array_values( $msgSpecs ); 03796 $s = $wrap; 03797 foreach ( $msgSpecs as $n => $spec ) { 03798 if ( is_array( $spec ) ) { 03799 $args = $spec; 03800 $name = array_shift( $args ); 03801 if ( isset( $args['options'] ) ) { 03802 unset( $args['options'] ); 03803 wfDeprecated( 03804 'Adding "options" to ' . __METHOD__ . ' is no longer supported', 03805 '1.20' 03806 ); 03807 } 03808 } else { 03809 $args = array(); 03810 $name = $spec; 03811 } 03812 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s ); 03813 } 03814 $this->addWikiText( $s ); 03815 } 03816 03826 public function includeJQuery( array $modules = array() ) { 03827 return array(); 03828 } 03829 03835 public function enableTOC( $flag = true ) { 03836 $this->mEnableTOC = $flag; 03837 } 03838 03843 public function isTOCEnabled() { 03844 return $this->mEnableTOC; 03845 } 03846 03852 public function enableSectionEditLinks( $flag = true ) { 03853 $this->mEnableSectionEditLinks = $flag; 03854 } 03855 03860 public function sectionEditLinksEnabled() { 03861 return $this->mEnableSectionEditLinks; 03862 } 03863 }