MediaWiki  REL1_22
OutputPage.php
Go to the documentation of this file.
00001 <?php
00038 class OutputPage extends ContextSource {
00040     var $mMetatags = array();
00041 
00042     var $mLinktags = array();
00043     var $mCanonicalUrl = false;
00044 
00046     var $mExtStyles = array();
00047 
00049     var $mPagetitle = '';
00050 
00052     var $mBodytext = '';
00053 
00059     public $mDebugtext = '';
00060 
00062     var $mHTMLtitle = '';
00063 
00065     var $mIsarticle = false;
00066 
00071     var $mIsArticleRelated = true;
00072 
00077     var $mPrintable = false;
00078 
00085     private $mSubtitle = array();
00086 
00087     var $mRedirect = '';
00088     var $mStatusCode;
00089 
00094     var $mLastModified = '';
00095 
00106     var $mETag = false;
00107 
00108     var $mCategoryLinks = array();
00109     var $mCategories = array();
00110 
00112     var $mLanguageLinks = array();
00113 
00120     var $mScripts = '';
00121 
00125     var $mInlineStyles = '';
00126 
00127     //
00128     var $mLinkColours;
00129 
00134     var $mPageLinkTitle = '';
00135 
00137     var $mHeadItems = array();
00138 
00139     // @todo FIXME: Next variables probably comes from the resource loader
00140     var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array();
00141     var $mResourceLoader;
00142     var $mJsConfigVars = array();
00143 
00145     var $mInlineMsg = array();
00146 
00147     var $mTemplateIds = array();
00148     var $mImageTimeKeys = array();
00149 
00150     var $mRedirectCode = '';
00151 
00152     var $mFeedLinksAppendQuery = null;
00153 
00159     protected $mAllowedModuleOrigin = ResourceLoaderModule::ORIGIN_ALL;
00160 
00165     var $mDoNothing = false;
00166 
00167     // Parser related.
00168     var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
00169 
00174     protected $mParserOptions = null;
00175 
00182     var $mFeedLinks = array();
00183 
00184     // Gwicke work on squid caching? Roughly from 2003.
00185     var $mEnableClientCache = true;
00186 
00191     var $mArticleBodyOnly = false;
00192 
00193     var $mNewSectionLink = false;
00194     var $mHideNewSectionLink = false;
00195 
00201     var $mNoGallery = false;
00202 
00203     // should be private.
00204     var $mPageTitleActionText = '';
00205     var $mParseWarnings = array();
00206 
00207     // Cache stuff. Looks like mEnableClientCache
00208     var $mSquidMaxage = 0;
00209 
00210     // @todo document
00211     var $mPreventClickjacking = true;
00212 
00214     var $mRevisionId = null;
00215     private $mRevisionTimestamp = null;
00216 
00217     var $mFileVersion = null;
00218 
00227     var $styles = array();
00228 
00232     protected $mJQueryDone = false;
00233 
00234     private $mIndexPolicy = 'index';
00235     private $mFollowPolicy = 'follow';
00236     private $mVaryHeader = array(
00237         'Accept-Encoding' => array( 'list-contains=gzip' ),
00238     );
00239 
00246     private $mRedirectedFrom = null;
00247 
00251     private $mProperties = array();
00252 
00256     private $mTarget = null;
00257 
00261     private $mEnableTOC = true;
00262 
00268     function __construct( IContextSource $context = null ) {
00269         if ( $context === null ) {
00270             # Extensions should use `new RequestContext` instead of `new OutputPage` now.
00271             wfDeprecated( __METHOD__, '1.18' );
00272         } else {
00273             $this->setContext( $context );
00274         }
00275     }
00276 
00283     public function redirect( $url, $responsecode = '302' ) {
00284         # Strip newlines as a paranoia check for header injection in PHP<5.1.2
00285         $this->mRedirect = str_replace( "\n", '', $url );
00286         $this->mRedirectCode = $responsecode;
00287     }
00288 
00294     public function getRedirect() {
00295         return $this->mRedirect;
00296     }
00297 
00303     public function setStatusCode( $statusCode ) {
00304         $this->mStatusCode = $statusCode;
00305     }
00306 
00314     function addMeta( $name, $val ) {
00315         array_push( $this->mMetatags, array( $name, $val ) );
00316     }
00317 
00325     function addLink( $linkarr ) {
00326         array_push( $this->mLinktags, $linkarr );
00327     }
00328 
00336     function addMetadataLink( $linkarr ) {
00337         $linkarr['rel'] = $this->getMetadataAttribute();
00338         $this->addLink( $linkarr );
00339     }
00340 
00345     function setCanonicalUrl( $url ) {
00346         $this->mCanonicalUrl = $url;
00347     }
00348 
00354     public function getMetadataAttribute() {
00355         # note: buggy CC software only reads first "meta" link
00356         static $haveMeta = false;
00357         if ( $haveMeta ) {
00358             return 'alternate meta';
00359         } else {
00360             $haveMeta = true;
00361             return 'meta';
00362         }
00363     }
00364 
00370     function addScript( $script ) {
00371         $this->mScripts .= $script . "\n";
00372     }
00373 
00382     public function addExtensionStyle( $url ) {
00383         array_push( $this->mExtStyles, $url );
00384     }
00385 
00391     function getExtStyle() {
00392         return $this->mExtStyles;
00393     }
00394 
00402     public function addScriptFile( $file, $version = null ) {
00403         global $wgStylePath, $wgStyleVersion;
00404         // See if $file parameter is an absolute URL or begins with a slash
00405         if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
00406             $path = $file;
00407         } else {
00408             $path = "{$wgStylePath}/common/{$file}";
00409         }
00410         if ( is_null( $version ) ) {
00411             $version = $wgStyleVersion;
00412         }
00413         $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
00414     }
00415 
00421     public function addInlineScript( $script ) {
00422         $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
00423     }
00424 
00430     function getScript() {
00431         return $this->mScripts . $this->getHeadItems();
00432     }
00433 
00442     protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ) {
00443         $resourceLoader = $this->getResourceLoader();
00444         $filteredModules = array();
00445         foreach ( $modules as $val ) {
00446             $module = $resourceLoader->getModule( $val );
00447             if ( $module instanceof ResourceLoaderModule
00448                 && $module->getOrigin() <= $this->getAllowedModules( $type )
00449                 && ( is_null( $position ) || $module->getPosition() == $position )
00450                 && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) ) )
00451             {
00452                 $filteredModules[] = $val;
00453             }
00454         }
00455         return $filteredModules;
00456     }
00457 
00466     public function getModules( $filter = false, $position = null, $param = 'mModules' ) {
00467         $modules = array_values( array_unique( $this->$param ) );
00468         return $filter
00469             ? $this->filterModules( $modules, $position )
00470             : $modules;
00471     }
00472 
00480     public function addModules( $modules ) {
00481         $this->mModules = array_merge( $this->mModules, (array)$modules );
00482     }
00483 
00492     public function getModuleScripts( $filter = false, $position = null ) {
00493         return $this->getModules( $filter, $position, 'mModuleScripts' );
00494     }
00495 
00503     public function addModuleScripts( $modules ) {
00504         $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
00505     }
00506 
00515     public function getModuleStyles( $filter = false, $position = null ) {
00516         return $this->getModules( $filter, $position, 'mModuleStyles' );
00517     }
00518 
00528     public function addModuleStyles( $modules ) {
00529         $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
00530     }
00531 
00540     public function getModuleMessages( $filter = false, $position = null ) {
00541         return $this->getModules( $filter, $position, 'mModuleMessages' );
00542     }
00543 
00551     public function addModuleMessages( $modules ) {
00552         $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
00553     }
00554 
00558     public function getTarget() {
00559         return $this->mTarget;
00560     }
00561 
00567     public function setTarget( $target ) {
00568         $this->mTarget = $target;
00569     }
00570 
00576     function getHeadItemsArray() {
00577         return $this->mHeadItems;
00578     }
00579 
00585     function getHeadItems() {
00586         $s = '';
00587         foreach ( $this->mHeadItems as $item ) {
00588             $s .= $item;
00589         }
00590         return $s;
00591     }
00592 
00599     public function addHeadItem( $name, $value ) {
00600         $this->mHeadItems[$name] = $value;
00601     }
00602 
00609     public function hasHeadItem( $name ) {
00610         return isset( $this->mHeadItems[$name] );
00611     }
00612 
00618     function setETag( $tag ) {
00619         $this->mETag = $tag;
00620     }
00621 
00629     public function setArticleBodyOnly( $only ) {
00630         $this->mArticleBodyOnly = $only;
00631     }
00632 
00638     public function getArticleBodyOnly() {
00639         return $this->mArticleBodyOnly;
00640     }
00641 
00649     public function setProperty( $name, $value ) {
00650         $this->mProperties[$name] = $value;
00651     }
00652 
00660     public function getProperty( $name ) {
00661         if ( isset( $this->mProperties[$name] ) ) {
00662             return $this->mProperties[$name];
00663         } else {
00664             return null;
00665         }
00666     }
00667 
00679     public function checkLastModified( $timestamp ) {
00680         global $wgCachePages, $wgCacheEpoch, $wgUseSquid, $wgSquidMaxage;
00681 
00682         if ( !$timestamp || $timestamp == '19700101000000' ) {
00683             wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
00684             return false;
00685         }
00686         if ( !$wgCachePages ) {
00687             wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
00688             return false;
00689         }
00690         if ( $this->getUser()->getOption( 'nocache' ) ) {
00691             wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
00692             return false;
00693         }
00694 
00695         $timestamp = wfTimestamp( TS_MW, $timestamp );
00696         $modifiedTimes = array(
00697             'page' => $timestamp,
00698             'user' => $this->getUser()->getTouched(),
00699             'epoch' => $wgCacheEpoch
00700         );
00701         if ( $wgUseSquid ) {
00702             // bug 44570: the core page itself may not change, but resources might
00703             $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $wgSquidMaxage );
00704         }
00705         wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
00706 
00707         $maxModified = max( $modifiedTimes );
00708         $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
00709 
00710         $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
00711         if ( $clientHeader === false ) {
00712             wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false );
00713             return false;
00714         }
00715 
00716         # IE sends sizes after the date like this:
00717         # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
00718         # this breaks strtotime().
00719         $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
00720 
00721         wfSuppressWarnings(); // E_STRICT system time bitching
00722         $clientHeaderTime = strtotime( $clientHeader );
00723         wfRestoreWarnings();
00724         if ( !$clientHeaderTime ) {
00725             wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
00726             return false;
00727         }
00728         $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
00729 
00730         # Make debug info
00731         $info = '';
00732         foreach ( $modifiedTimes as $name => $value ) {
00733             if ( $info !== '' ) {
00734                 $info .= ', ';
00735             }
00736             $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
00737         }
00738 
00739         wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
00740             wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
00741         wfDebug( __METHOD__ . ": effective Last-Modified: " .
00742             wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
00743         if ( $clientHeaderTime < $maxModified ) {
00744             wfDebug( __METHOD__ . ": STALE, $info\n", false );
00745             return false;
00746         }
00747 
00748         # Not modified
00749         # Give a 304 response code and disable body output
00750         wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
00751         ini_set( 'zlib.output_compression', 0 );
00752         $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" );
00753         $this->sendCacheControl();
00754         $this->disable();
00755 
00756         // Don't output a compressed blob when using ob_gzhandler;
00757         // it's technically against HTTP spec and seems to confuse
00758         // Firefox when the response gets split over two packets.
00759         wfClearOutputBuffers();
00760 
00761         return true;
00762     }
00763 
00770     public function setLastModified( $timestamp ) {
00771         $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
00772     }
00773 
00782     public function setRobotPolicy( $policy ) {
00783         $policy = Article::formatRobotPolicy( $policy );
00784 
00785         if ( isset( $policy['index'] ) ) {
00786             $this->setIndexPolicy( $policy['index'] );
00787         }
00788         if ( isset( $policy['follow'] ) ) {
00789             $this->setFollowPolicy( $policy['follow'] );
00790         }
00791     }
00792 
00800     public function setIndexPolicy( $policy ) {
00801         $policy = trim( $policy );
00802         if ( in_array( $policy, array( 'index', 'noindex' ) ) ) {
00803             $this->mIndexPolicy = $policy;
00804         }
00805     }
00806 
00814     public function setFollowPolicy( $policy ) {
00815         $policy = trim( $policy );
00816         if ( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
00817             $this->mFollowPolicy = $policy;
00818         }
00819     }
00820 
00827     public function setPageTitleActionText( $text ) {
00828         $this->mPageTitleActionText = $text;
00829     }
00830 
00836     public function getPageTitleActionText() {
00837         if ( isset( $this->mPageTitleActionText ) ) {
00838             return $this->mPageTitleActionText;
00839         }
00840         return '';
00841     }
00842 
00849     public function setHTMLTitle( $name ) {
00850         if ( $name instanceof Message ) {
00851             $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
00852         } else {
00853             $this->mHTMLtitle = $name;
00854         }
00855     }
00856 
00862     public function getHTMLTitle() {
00863         return $this->mHTMLtitle;
00864     }
00865 
00871     public function setRedirectedFrom( $t ) {
00872         $this->mRedirectedFrom = $t;
00873     }
00874 
00883     public function setPageTitle( $name ) {
00884         if ( $name instanceof Message ) {
00885             $name = $name->setContext( $this->getContext() )->text();
00886         }
00887 
00888         # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
00889         # but leave "<i>foobar</i>" alone
00890         $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
00891         $this->mPagetitle = $nameWithTags;
00892 
00893         # change "<i>foo&amp;bar</i>" to "foo&bar"
00894         $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) );
00895     }
00896 
00902     public function getPageTitle() {
00903         return $this->mPagetitle;
00904     }
00905 
00911     public function setTitle( Title $t ) {
00912         $this->getContext()->setTitle( $t );
00913     }
00914 
00920     public function setSubtitle( $str ) {
00921         $this->clearSubtitle();
00922         $this->addSubtitle( $str );
00923     }
00924 
00931     public function appendSubtitle( $str ) {
00932         $this->addSubtitle( $str );
00933     }
00934 
00940     public function addSubtitle( $str ) {
00941         if ( $str instanceof Message ) {
00942             $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
00943         } else {
00944             $this->mSubtitle[] = $str;
00945         }
00946     }
00947 
00953     public function addBacklinkSubtitle( Title $title ) {
00954         $query = array();
00955         if ( $title->isRedirect() ) {
00956             $query['redirect'] = 'no';
00957         }
00958         $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) );
00959     }
00960 
00964     public function clearSubtitle() {
00965         $this->mSubtitle = array();
00966     }
00967 
00973     public function getSubtitle() {
00974         return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
00975     }
00976 
00981     public function setPrintable() {
00982         $this->mPrintable = true;
00983     }
00984 
00990     public function isPrintable() {
00991         return $this->mPrintable;
00992     }
00993 
00997     public function disable() {
00998         $this->mDoNothing = true;
00999     }
01000 
01006     public function isDisabled() {
01007         return $this->mDoNothing;
01008     }
01009 
01015     public function showNewSectionLink() {
01016         return $this->mNewSectionLink;
01017     }
01018 
01024     public function forceHideNewSectionLink() {
01025         return $this->mHideNewSectionLink;
01026     }
01027 
01036     public function setSyndicated( $show = true ) {
01037         if ( $show ) {
01038             $this->setFeedAppendQuery( false );
01039         } else {
01040             $this->mFeedLinks = array();
01041         }
01042     }
01043 
01053     public function setFeedAppendQuery( $val ) {
01054         global $wgAdvertisedFeedTypes;
01055 
01056         $this->mFeedLinks = array();
01057 
01058         foreach ( $wgAdvertisedFeedTypes as $type ) {
01059             $query = "feed=$type";
01060             if ( is_string( $val ) ) {
01061                 $query .= '&' . $val;
01062             }
01063             $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
01064         }
01065     }
01066 
01073     public function addFeedLink( $format, $href ) {
01074         global $wgAdvertisedFeedTypes;
01075 
01076         if ( in_array( $format, $wgAdvertisedFeedTypes ) ) {
01077             $this->mFeedLinks[$format] = $href;
01078         }
01079     }
01080 
01085     public function isSyndicated() {
01086         return count( $this->mFeedLinks ) > 0;
01087     }
01088 
01093     public function getSyndicationLinks() {
01094         return $this->mFeedLinks;
01095     }
01096 
01102     public function getFeedAppendQuery() {
01103         return $this->mFeedLinksAppendQuery;
01104     }
01105 
01113     public function setArticleFlag( $v ) {
01114         $this->mIsarticle = $v;
01115         if ( $v ) {
01116             $this->mIsArticleRelated = $v;
01117         }
01118     }
01119 
01126     public function isArticle() {
01127         return $this->mIsarticle;
01128     }
01129 
01136     public function setArticleRelated( $v ) {
01137         $this->mIsArticleRelated = $v;
01138         if ( !$v ) {
01139             $this->mIsarticle = false;
01140         }
01141     }
01142 
01148     public function isArticleRelated() {
01149         return $this->mIsArticleRelated;
01150     }
01151 
01158     public function addLanguageLinks( $newLinkArray ) {
01159         $this->mLanguageLinks += $newLinkArray;
01160     }
01161 
01168     public function setLanguageLinks( $newLinkArray ) {
01169         $this->mLanguageLinks = $newLinkArray;
01170     }
01171 
01177     public function getLanguageLinks() {
01178         return $this->mLanguageLinks;
01179     }
01180 
01186     public function addCategoryLinks( $categories ) {
01187         global $wgContLang;
01188 
01189         if ( !is_array( $categories ) || count( $categories ) == 0 ) {
01190             return;
01191         }
01192 
01193         # Add the links to a LinkBatch
01194         $arr = array( NS_CATEGORY => $categories );
01195         $lb = new LinkBatch;
01196         $lb->setArray( $arr );
01197 
01198         # Fetch existence plus the hiddencat property
01199         $dbr = wfGetDB( DB_SLAVE );
01200         $res = $dbr->select( array( 'page', 'page_props' ),
01201             array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ),
01202             $lb->constructSet( 'page', $dbr ),
01203             __METHOD__,
01204             array(),
01205             array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) )
01206         );
01207 
01208         # Add the results to the link cache
01209         $lb->addResultToCache( LinkCache::singleton(), $res );
01210 
01211         # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
01212         $categories = array_combine(
01213             array_keys( $categories ),
01214             array_fill( 0, count( $categories ), 'normal' )
01215         );
01216 
01217         # Mark hidden categories
01218         foreach ( $res as $row ) {
01219             if ( isset( $row->pp_value ) ) {
01220                 $categories[$row->page_title] = 'hidden';
01221             }
01222         }
01223 
01224         # Add the remaining categories to the skin
01225         if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
01226             foreach ( $categories as $category => $type ) {
01227                 $origcategory = $category;
01228                 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
01229                 $wgContLang->findVariantLink( $category, $title, true );
01230                 if ( $category != $origcategory ) {
01231                     if ( array_key_exists( $category, $categories ) ) {
01232                         continue;
01233                     }
01234                 }
01235                 $text = $wgContLang->convertHtml( $title->getText() );
01236                 $this->mCategories[] = $title->getText();
01237                 $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
01238             }
01239         }
01240     }
01241 
01247     public function setCategoryLinks( $categories ) {
01248         $this->mCategoryLinks = array();
01249         $this->addCategoryLinks( $categories );
01250     }
01251 
01260     public function getCategoryLinks() {
01261         return $this->mCategoryLinks;
01262     }
01263 
01269     public function getCategories() {
01270         return $this->mCategories;
01271     }
01272 
01279     public function disallowUserJs() {
01280         $this->reduceAllowedModuleOrigin( ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL );
01281     }
01282 
01289     public function isUserJsAllowed() {
01290         wfDeprecated( __METHOD__, '1.18' );
01291         return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
01292     }
01293 
01302     public function getAllowedModules( $type = null ) {
01303         return $this->mAllowedModuleOrigin;
01304     }
01305 
01315     public function setAllowedModules( $type, $level ) {
01316         wfDeprecated( __METHOD__, '1.24' );
01317         $this->reduceAllowedModuleOrigin( $level );
01318     }
01319 
01329     public function reduceAllowedModules( $type, $level ) {
01330         wfDeprecated( __METHOD__, '1.24' );
01331         $this->reduceAllowedModuleOrigin( $level );
01332     }
01333 
01342     public function reduceAllowedModuleOrigin( $level ) {
01343         $this->mAllowedModuleOrigin = min( $this->mAllowedModuleOrigin, $level );
01344     }
01345 
01351     public function prependHTML( $text ) {
01352         $this->mBodytext = $text . $this->mBodytext;
01353     }
01354 
01360     public function addHTML( $text ) {
01361         $this->mBodytext .= $text;
01362     }
01363 
01373     public function addElement( $element, $attribs = array(), $contents = '' ) {
01374         $this->addHTML( Html::element( $element, $attribs, $contents ) );
01375     }
01376 
01380     public function clearHTML() {
01381         $this->mBodytext = '';
01382     }
01383 
01389     public function getHTML() {
01390         return $this->mBodytext;
01391     }
01392 
01400     public function parserOptions( $options = null ) {
01401         if ( !$this->mParserOptions ) {
01402             $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
01403             $this->mParserOptions->setEditSection( false );
01404         }
01405         return wfSetVar( $this->mParserOptions, $options );
01406     }
01407 
01415     public function setRevisionId( $revid ) {
01416         $val = is_null( $revid ) ? null : intval( $revid );
01417         return wfSetVar( $this->mRevisionId, $val );
01418     }
01419 
01425     public function getRevisionId() {
01426         return $this->mRevisionId;
01427     }
01428 
01436     public function setRevisionTimestamp( $timestamp ) {
01437         return wfSetVar( $this->mRevisionTimestamp, $timestamp );
01438     }
01439 
01446     public function getRevisionTimestamp() {
01447         return $this->mRevisionTimestamp;
01448     }
01449 
01456     public function setFileVersion( $file ) {
01457         $val = null;
01458         if ( $file instanceof File && $file->exists() ) {
01459             $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() );
01460         }
01461         return wfSetVar( $this->mFileVersion, $val, true );
01462     }
01463 
01469     public function getFileVersion() {
01470         return $this->mFileVersion;
01471     }
01472 
01479     public function getTemplateIds() {
01480         return $this->mTemplateIds;
01481     }
01482 
01489     public function getFileSearchOptions() {
01490         return $this->mImageTimeKeys;
01491     }
01492 
01501     public function addWikiText( $text, $linestart = true, $interface = true ) {
01502         $title = $this->getTitle(); // Work around E_STRICT
01503         if ( !$title ) {
01504             throw new MWException( 'Title is null' );
01505         }
01506         $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
01507     }
01508 
01516     public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
01517         $this->addWikiTextTitle( $text, $title, $linestart );
01518     }
01519 
01527     function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
01528         $this->addWikiTextTitle( $text, $title, $linestart, true );
01529     }
01530 
01537     public function addWikiTextTidy( $text, $linestart = true ) {
01538         $title = $this->getTitle();
01539         $this->addWikiTextTitleTidy( $text, $title, $linestart );
01540     }
01541 
01552     public function addWikiTextTitle( $text, Title $title, $linestart, $tidy = false, $interface = false ) {
01553         global $wgParser;
01554 
01555         wfProfileIn( __METHOD__ );
01556 
01557         $popts = $this->parserOptions();
01558         $oldTidy = $popts->setTidy( $tidy );
01559         $popts->setInterfaceMessage( (bool)$interface );
01560 
01561         $parserOutput = $wgParser->parse(
01562             $text, $title, $popts,
01563             $linestart, true, $this->mRevisionId
01564         );
01565 
01566         $popts->setTidy( $oldTidy );
01567 
01568         $this->addParserOutput( $parserOutput );
01569 
01570         wfProfileOut( __METHOD__ );
01571     }
01572 
01578     public function addParserOutputNoText( &$parserOutput ) {
01579         $this->mLanguageLinks += $parserOutput->getLanguageLinks();
01580         $this->addCategoryLinks( $parserOutput->getCategories() );
01581         $this->mNewSectionLink = $parserOutput->getNewSection();
01582         $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
01583 
01584         $this->mParseWarnings = $parserOutput->getWarnings();
01585         if ( !$parserOutput->isCacheable() ) {
01586             $this->enableClientCache( false );
01587         }
01588         $this->mNoGallery = $parserOutput->getNoGallery();
01589         $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
01590         $this->addModules( $parserOutput->getModules() );
01591         $this->addModuleScripts( $parserOutput->getModuleScripts() );
01592         $this->addModuleStyles( $parserOutput->getModuleStyles() );
01593         $this->addModuleMessages( $parserOutput->getModuleMessages() );
01594         $this->mPreventClickjacking = $this->mPreventClickjacking
01595             || $parserOutput->preventClickjacking();
01596 
01597         // Template versioning...
01598         foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
01599             if ( isset( $this->mTemplateIds[$ns] ) ) {
01600                 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
01601             } else {
01602                 $this->mTemplateIds[$ns] = $dbks;
01603             }
01604         }
01605         // File versioning...
01606         foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
01607             $this->mImageTimeKeys[$dbk] = $data;
01608         }
01609 
01610         // Hooks registered in the object
01611         global $wgParserOutputHooks;
01612         foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
01613             list( $hookName, $data ) = $hookInfo;
01614             if ( isset( $wgParserOutputHooks[$hookName] ) ) {
01615                 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data );
01616             }
01617         }
01618 
01619         // Link flags are ignored for now, but may in the future be
01620         // used to mark individual language links.
01621         $linkFlags = array();
01622         wfRunHooks( 'LanguageLinks', array( $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ) );
01623         wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
01624     }
01625 
01631     function addParserOutput( &$parserOutput ) {
01632         $this->addParserOutputNoText( $parserOutput );
01633         $parserOutput->setTOCEnabled( $this->mEnableTOC );
01634         $text = $parserOutput->getText();
01635         wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
01636         $this->addHTML( $text );
01637     }
01638 
01644     public function addTemplate( &$template ) {
01645         ob_start();
01646         $template->execute();
01647         $this->addHTML( ob_get_contents() );
01648         ob_end_clean();
01649     }
01650 
01665     public function parse( $text, $linestart = true, $interface = false, $language = null ) {
01666         global $wgParser;
01667 
01668         if ( is_null( $this->getTitle() ) ) {
01669             throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
01670         }
01671 
01672         $popts = $this->parserOptions();
01673         if ( $interface ) {
01674             $popts->setInterfaceMessage( true );
01675         }
01676         if ( $language !== null ) {
01677             $oldLang = $popts->setTargetLanguage( $language );
01678         }
01679 
01680         $parserOutput = $wgParser->parse(
01681             $text, $this->getTitle(), $popts,
01682             $linestart, true, $this->mRevisionId
01683         );
01684 
01685         if ( $interface ) {
01686             $popts->setInterfaceMessage( false );
01687         }
01688         if ( $language !== null ) {
01689             $popts->setTargetLanguage( $oldLang );
01690         }
01691 
01692         return $parserOutput->getText();
01693     }
01694 
01705     public function parseInline( $text, $linestart = true, $interface = false ) {
01706         $parsed = $this->parse( $text, $linestart, $interface );
01707 
01708         $m = array();
01709         if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) {
01710             $parsed = $m[1];
01711         }
01712 
01713         return $parsed;
01714     }
01715 
01721     public function setSquidMaxage( $maxage ) {
01722         $this->mSquidMaxage = $maxage;
01723     }
01724 
01732     public function enableClientCache( $state ) {
01733         return wfSetVar( $this->mEnableClientCache, $state );
01734     }
01735 
01741     function getCacheVaryCookies() {
01742         global $wgCookiePrefix, $wgCacheVaryCookies;
01743         static $cookies;
01744         if ( $cookies === null ) {
01745             $cookies = array_merge(
01746                 array(
01747                     "{$wgCookiePrefix}Token",
01748                     "{$wgCookiePrefix}LoggedOut",
01749                     "forceHTTPS",
01750                     session_name()
01751                 ),
01752                 $wgCacheVaryCookies
01753             );
01754             wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) );
01755         }
01756         return $cookies;
01757     }
01758 
01765     function haveCacheVaryCookies() {
01766         $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
01767         if ( $cookieHeader === false ) {
01768             return false;
01769         }
01770         $cvCookies = $this->getCacheVaryCookies();
01771         foreach ( $cvCookies as $cookieName ) {
01772             # Check for a simple string match, like the way squid does it
01773             if ( strpos( $cookieHeader, $cookieName ) !== false ) {
01774                 wfDebug( __METHOD__ . ": found $cookieName\n" );
01775                 return true;
01776             }
01777         }
01778         wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
01779         return false;
01780     }
01781 
01790     public function addVaryHeader( $header, $option = null ) {
01791         if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
01792             $this->mVaryHeader[$header] = (array)$option;
01793         } elseif ( is_array( $option ) ) {
01794             if ( is_array( $this->mVaryHeader[$header] ) ) {
01795                 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
01796             } else {
01797                 $this->mVaryHeader[$header] = $option;
01798             }
01799         }
01800         $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] );
01801     }
01802 
01809     public function getVaryHeader() {
01810         return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) );
01811     }
01812 
01818     public function getXVO() {
01819         $cvCookies = $this->getCacheVaryCookies();
01820 
01821         $cookiesOption = array();
01822         foreach ( $cvCookies as $cookieName ) {
01823             $cookiesOption[] = 'string-contains=' . $cookieName;
01824         }
01825         $this->addVaryHeader( 'Cookie', $cookiesOption );
01826 
01827         $headers = array();
01828         foreach ( $this->mVaryHeader as $header => $option ) {
01829             $newheader = $header;
01830             if ( is_array( $option ) && count( $option ) > 0 ) {
01831                 $newheader .= ';' . implode( ';', $option );
01832             }
01833             $headers[] = $newheader;
01834         }
01835         $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
01836 
01837         return $xvo;
01838     }
01839 
01848     function addAcceptLanguage() {
01849         $lang = $this->getTitle()->getPageLanguage();
01850         if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
01851             $variants = $lang->getVariants();
01852             $aloption = array();
01853             foreach ( $variants as $variant ) {
01854                 if ( $variant === $lang->getCode() ) {
01855                     continue;
01856                 } else {
01857                     $aloption[] = 'string-contains=' . $variant;
01858 
01859                     // IE and some other browsers use BCP 47 standards in
01860                     // their Accept-Language header, like "zh-CN" or "zh-Hant".
01861                     // We should handle these too.
01862                     $variantBCP47 = wfBCP47( $variant );
01863                     if ( $variantBCP47 !== $variant ) {
01864                         $aloption[] = 'string-contains=' . $variantBCP47;
01865                     }
01866                 }
01867             }
01868             $this->addVaryHeader( 'Accept-Language', $aloption );
01869         }
01870     }
01871 
01882     public function preventClickjacking( $enable = true ) {
01883         $this->mPreventClickjacking = $enable;
01884     }
01885 
01891     public function allowClickjacking() {
01892         $this->mPreventClickjacking = false;
01893     }
01894 
01901     public function getPreventClickjacking() {
01902         return $this->mPreventClickjacking;
01903     }
01904 
01912     public function getFrameOptions() {
01913         global $wgBreakFrames, $wgEditPageFrameOptions;
01914         if ( $wgBreakFrames ) {
01915             return 'DENY';
01916         } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) {
01917             return $wgEditPageFrameOptions;
01918         }
01919         return false;
01920     }
01921 
01925     public function sendCacheControl() {
01926         global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO;
01927 
01928         $response = $this->getRequest()->response();
01929         if ( $wgUseETag && $this->mETag ) {
01930             $response->header( "ETag: $this->mETag" );
01931         }
01932 
01933         $this->addVaryHeader( 'Cookie' );
01934         $this->addAcceptLanguage();
01935 
01936         # don't serve compressed data to clients who can't handle it
01937         # maintain different caches for logged-in users and non-logged in ones
01938         $response->header( $this->getVaryHeader() );
01939 
01940         if ( $wgUseXVO ) {
01941             # Add an X-Vary-Options header for Squid with Wikimedia patches
01942             $response->header( $this->getXVO() );
01943         }
01944 
01945         if ( $this->mEnableClientCache ) {
01946             if (
01947                 $wgUseSquid && session_id() == '' && !$this->isPrintable() &&
01948                 $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies()
01949             ) {
01950                 if ( $wgUseESI ) {
01951                     # We'll purge the proxy cache explicitly, but require end user agents
01952                     # to revalidate against the proxy on each visit.
01953                     # Surrogate-Control controls our Squid, Cache-Control downstream caches
01954                     wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false );
01955                     # start with a shorter timeout for initial testing
01956                     # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
01957                     $response->header( 'Surrogate-Control: max-age=' . $wgSquidMaxage . '+' . $this->mSquidMaxage . ', content="ESI/1.0"' );
01958                     $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
01959                 } else {
01960                     # We'll purge the proxy cache for anons explicitly, but require end user agents
01961                     # to revalidate against the proxy on each visit.
01962                     # IMPORTANT! The Squid needs to replace the Cache-Control header with
01963                     # Cache-Control: s-maxage=0, must-revalidate, max-age=0
01964                     wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false );
01965                     # start with a shorter timeout for initial testing
01966                     # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
01967                     $response->header( 'Cache-Control: s-maxage=' . $this->mSquidMaxage . ', must-revalidate, max-age=0' );
01968                 }
01969             } else {
01970                 # We do want clients to cache if they can, but they *must* check for updates
01971                 # on revisiting the page.
01972                 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false );
01973                 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
01974                 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
01975             }
01976             if ( $this->mLastModified ) {
01977                 $response->header( "Last-Modified: {$this->mLastModified}" );
01978             }
01979         } else {
01980             wfDebug( __METHOD__ . ": no caching **\n", false );
01981 
01982             # In general, the absence of a last modified header should be enough to prevent
01983             # the client from using its cache. We send a few other things just to make sure.
01984             $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
01985             $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
01986             $response->header( 'Pragma: no-cache' );
01987         }
01988     }
01989 
01999     public static function getStatusMessage( $code ) {
02000         wfDeprecated( __METHOD__, '1.18' );
02001         return HttpStatus::getMessage( $code );
02002     }
02003 
02008     public function output() {
02009         global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP,
02010             $wgUseAjax, $wgResponsiveImages;
02011 
02012         if ( $this->mDoNothing ) {
02013             return;
02014         }
02015 
02016         wfProfileIn( __METHOD__ );
02017 
02018         $response = $this->getRequest()->response();
02019 
02020         if ( $this->mRedirect != '' ) {
02021             # Standards require redirect URLs to be absolute
02022             $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
02023 
02024             $redirect = $this->mRedirect;
02025             $code = $this->mRedirectCode;
02026 
02027             if ( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
02028                 if ( $code == '301' || $code == '303' ) {
02029                     if ( !$wgDebugRedirects ) {
02030                         $message = HttpStatus::getMessage( $code );
02031                         $response->header( "HTTP/1.1 $code $message" );
02032                     }
02033                     $this->mLastModified = wfTimestamp( TS_RFC2822 );
02034                 }
02035                 if ( $wgVaryOnXFP ) {
02036                     $this->addVaryHeader( 'X-Forwarded-Proto' );
02037                 }
02038                 $this->sendCacheControl();
02039 
02040                 $response->header( "Content-Type: text/html; charset=utf-8" );
02041                 if ( $wgDebugRedirects ) {
02042                     $url = htmlspecialchars( $redirect );
02043                     print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
02044                     print "<p>Location: <a href=\"$url\">$url</a></p>\n";
02045                     print "</body>\n</html>\n";
02046                 } else {
02047                     $response->header( 'Location: ' . $redirect );
02048                 }
02049             }
02050 
02051             wfProfileOut( __METHOD__ );
02052             return;
02053         } elseif ( $this->mStatusCode ) {
02054             $message = HttpStatus::getMessage( $this->mStatusCode );
02055             if ( $message ) {
02056                 $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
02057             }
02058         }
02059 
02060         # Buffer output; final headers may depend on later processing
02061         ob_start();
02062 
02063         $response->header( "Content-type: $wgMimeType; charset=UTF-8" );
02064         $response->header( 'Content-language: ' . $wgLanguageCode );
02065 
02066         // Prevent framing, if requested
02067         $frameOptions = $this->getFrameOptions();
02068         if ( $frameOptions ) {
02069             $response->header( "X-Frame-Options: $frameOptions" );
02070         }
02071 
02072         if ( $this->mArticleBodyOnly ) {
02073             echo $this->mBodytext;
02074         } else {
02075 
02076             $sk = $this->getSkin();
02077             // add skin specific modules
02078             $modules = $sk->getDefaultModules();
02079 
02080             // enforce various default modules for all skins
02081             $coreModules = array(
02082                 // keep this list as small as possible
02083                 'mediawiki.page.startup',
02084                 'mediawiki.user',
02085             );
02086 
02087             // Support for high-density display images if enabled
02088             if ( $wgResponsiveImages ) {
02089                 $coreModules[] = 'mediawiki.hidpi';
02090             }
02091 
02092             $this->addModules( $coreModules );
02093             foreach ( $modules as $group ) {
02094                 $this->addModules( $group );
02095             }
02096             MWDebug::addModules( $this );
02097             if ( $wgUseAjax ) {
02098                 // FIXME: deprecate? - not clear why this is useful
02099                 wfRunHooks( 'AjaxAddScript', array( &$this ) );
02100             }
02101 
02102             // Hook that allows last minute changes to the output page, e.g.
02103             // adding of CSS or Javascript by extensions.
02104             wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
02105 
02106             wfProfileIn( 'Output-skin' );
02107             $sk->outputPage();
02108             wfProfileOut( 'Output-skin' );
02109         }
02110 
02111         // This hook allows last minute changes to final overall output by modifying output buffer
02112         wfRunHooks( 'AfterFinalPageOutput', array( $this ) );
02113 
02114         $this->sendCacheControl();
02115 
02116         ob_end_flush();
02117 
02118         wfProfileOut( __METHOD__ );
02119     }
02120 
02127     public function out( $ins ) {
02128         wfDeprecated( __METHOD__, '1.22' );
02129         print $ins;
02130     }
02131 
02136     function blockedPage() {
02137         throw new UserBlockedError( $this->getUser()->mBlock );
02138     }
02139 
02150     public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
02151         $this->setPageTitle( $pageTitle );
02152         if ( $htmlTitle !== false ) {
02153             $this->setHTMLTitle( $htmlTitle );
02154         }
02155         $this->setRobotPolicy( 'noindex,nofollow' );
02156         $this->setArticleRelated( false );
02157         $this->enableClientCache( false );
02158         $this->mRedirect = '';
02159         $this->clearSubtitle();
02160         $this->clearHTML();
02161     }
02162 
02174     public function showErrorPage( $title, $msg, $params = array() ) {
02175         if ( !$title instanceof Message ) {
02176             $title = $this->msg( $title );
02177         }
02178 
02179         $this->prepareErrorPage( $title );
02180 
02181         if ( $msg instanceof Message ) {
02182             $this->addHTML( $msg->parseAsBlock() );
02183         } else {
02184             $this->addWikiMsgArray( $msg, $params );
02185         }
02186 
02187         $this->returnToMain();
02188     }
02189 
02196     public function showPermissionsErrorPage( $errors, $action = null ) {
02197         // For some action (read, edit, create and upload), display a "login to do this action"
02198         // error if all of the following conditions are met:
02199         // 1. the user is not logged in
02200         // 2. the only error is insufficient permissions (i.e. no block or something else)
02201         // 3. the error can be avoided simply by logging in
02202         if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) )
02203             && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
02204             && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
02205             && ( User::groupHasPermission( 'user', $action )
02206             || User::groupHasPermission( 'autoconfirmed', $action ) )
02207         ) {
02208             $displayReturnto = null;
02209 
02210             # Due to bug 32276, if a user does not have read permissions,
02211             # $this->getTitle() will just give Special:Badtitle, which is
02212             # not especially useful as a returnto parameter. Use the title
02213             # from the request instead, if there was one.
02214             $request = $this->getRequest();
02215             $returnto = Title::newFromURL( $request->getVal( 'title', '' ) );
02216             if ( $action == 'edit' ) {
02217                 $msg = 'whitelistedittext';
02218                 $displayReturnto = $returnto;
02219             } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
02220                 $msg = 'nocreatetext';
02221             } elseif ( $action == 'upload' ) {
02222                 $msg = 'uploadnologintext';
02223             } else { # Read
02224                 $msg = 'loginreqpagetext';
02225                 $displayReturnto = Title::newMainPage();
02226             }
02227 
02228             $query = array();
02229 
02230             if ( $returnto ) {
02231                 $query['returnto'] = $returnto->getPrefixedText();
02232 
02233                 if ( !$request->wasPosted() ) {
02234                     $returntoquery = $request->getValues();
02235                     unset( $returntoquery['title'] );
02236                     unset( $returntoquery['returnto'] );
02237                     unset( $returntoquery['returntoquery'] );
02238                     $query['returntoquery'] = wfArrayToCgi( $returntoquery );
02239                 }
02240             }
02241             $loginLink = Linker::linkKnown(
02242                 SpecialPage::getTitleFor( 'Userlogin' ),
02243                 $this->msg( 'loginreqlink' )->escaped(),
02244                 array(),
02245                 $query
02246             );
02247 
02248             $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
02249             $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
02250 
02251             # Don't return to a page the user can't read otherwise
02252             # we'll end up in a pointless loop
02253             if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
02254                 $this->returnToMain( null, $displayReturnto );
02255             }
02256         } else {
02257             $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
02258             $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
02259         }
02260     }
02261 
02268     public function versionRequired( $version ) {
02269         $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
02270 
02271         $this->addWikiMsg( 'versionrequiredtext', $version );
02272         $this->returnToMain();
02273     }
02274 
02281     public function permissionRequired( $permission ) {
02282         throw new PermissionsError( $permission );
02283     }
02284 
02290     public function loginToUse() {
02291         throw new PermissionsError( 'read' );
02292     }
02293 
02301     public function formatPermissionsErrorMessage( $errors, $action = null ) {
02302         if ( $action == null ) {
02303             $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
02304         } else {
02305             $action_desc = $this->msg( "action-$action" )->plain();
02306             $text = $this->msg(
02307                 'permissionserrorstext-withaction',
02308                 count( $errors ),
02309                 $action_desc
02310             )->plain() . "\n\n";
02311         }
02312 
02313         if ( count( $errors ) > 1 ) {
02314             $text .= '<ul class="permissions-errors">' . "\n";
02315 
02316             foreach ( $errors as $error ) {
02317                 $text .= '<li>';
02318                 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain();
02319                 $text .= "</li>\n";
02320             }
02321             $text .= '</ul>';
02322         } else {
02323             $text .= "<div class=\"permissions-errors\">\n" .
02324                     call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() .
02325                     "\n</div>";
02326         }
02327 
02328         return $text;
02329     }
02330 
02352     public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
02353         $this->setRobotPolicy( 'noindex,nofollow' );
02354         $this->setArticleRelated( false );
02355 
02356         // If no reason is given, just supply a default "I can't let you do
02357         // that, Dave" message.  Should only occur if called by legacy code.
02358         if ( $protected && empty( $reasons ) ) {
02359             $reasons[] = array( 'badaccess-group0' );
02360         }
02361 
02362         if ( !empty( $reasons ) ) {
02363             // Permissions error
02364             if ( $source ) {
02365                 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) );
02366                 $this->addBacklinkSubtitle( $this->getTitle() );
02367             } else {
02368                 $this->setPageTitle( $this->msg( 'badaccess' ) );
02369             }
02370             $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
02371         } else {
02372             // Wiki is read only
02373             throw new ReadOnlyError;
02374         }
02375 
02376         // Show source, if supplied
02377         if ( is_string( $source ) ) {
02378             $this->addWikiMsg( 'viewsourcetext' );
02379 
02380             $pageLang = $this->getTitle()->getPageLanguage();
02381             $params = array(
02382                 'id' => 'wpTextbox1',
02383                 'name' => 'wpTextbox1',
02384                 'cols' => $this->getUser()->getOption( 'cols' ),
02385                 'rows' => $this->getUser()->getOption( 'rows' ),
02386                 'readonly' => 'readonly',
02387                 'lang' => $pageLang->getHtmlCode(),
02388                 'dir' => $pageLang->getDir(),
02389             );
02390             $this->addHTML( Html::element( 'textarea', $params, $source ) );
02391 
02392             // Show templates used by this article
02393             $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() );
02394             $this->addHTML( "<div class='templatesUsed'>
02395 $templates
02396 </div>
02397 " );
02398         }
02399 
02400         # If the title doesn't exist, it's fairly pointless to print a return
02401         # link to it.  After all, you just tried editing it and couldn't, so
02402         # what's there to do there?
02403         if ( $this->getTitle()->exists() ) {
02404             $this->returnToMain( null, $this->getTitle() );
02405         }
02406     }
02407 
02412     public function rateLimited() {
02413         throw new ThrottledError;
02414     }
02415 
02425     public function showLagWarning( $lag ) {
02426         global $wgSlaveLagWarning, $wgSlaveLagCritical;
02427         if ( $lag >= $wgSlaveLagWarning ) {
02428             $message = $lag < $wgSlaveLagCritical
02429                 ? 'lag-warn-normal'
02430                 : 'lag-warn-high';
02431             $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
02432             $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) );
02433         }
02434     }
02435 
02436     public function showFatalError( $message ) {
02437         $this->prepareErrorPage( $this->msg( 'internalerror' ) );
02438 
02439         $this->addHTML( $message );
02440     }
02441 
02442     public function showUnexpectedValueError( $name, $val ) {
02443         $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
02444     }
02445 
02446     public function showFileCopyError( $old, $new ) {
02447         $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
02448     }
02449 
02450     public function showFileRenameError( $old, $new ) {
02451         $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
02452     }
02453 
02454     public function showFileDeleteError( $name ) {
02455         $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
02456     }
02457 
02458     public function showFileNotFoundError( $name ) {
02459         $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
02460     }
02461 
02470     public function addReturnTo( $title, $query = array(), $text = null, $options = array() ) {
02471         $link = $this->msg( 'returnto' )->rawParams(
02472             Linker::link( $title, $text, array(), $query, $options ) )->escaped();
02473         $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
02474     }
02475 
02484     public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
02485         if ( $returnto == null ) {
02486             $returnto = $this->getRequest()->getText( 'returnto' );
02487         }
02488 
02489         if ( $returntoquery == null ) {
02490             $returntoquery = $this->getRequest()->getText( 'returntoquery' );
02491         }
02492 
02493         if ( $returnto === '' ) {
02494             $returnto = Title::newMainPage();
02495         }
02496 
02497         if ( is_object( $returnto ) ) {
02498             $titleObj = $returnto;
02499         } else {
02500             $titleObj = Title::newFromText( $returnto );
02501         }
02502         if ( !is_object( $titleObj ) ) {
02503             $titleObj = Title::newMainPage();
02504         }
02505 
02506         $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
02507     }
02508 
02514     public function headElement( Skin $sk, $includeStyle = true ) {
02515         global $wgContLang, $wgMimeType;
02516 
02517         $userdir = $this->getLanguage()->getDir();
02518         $sitedir = $wgContLang->getDir();
02519 
02520         $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
02521 
02522         if ( $this->getHTMLTitle() == '' ) {
02523             $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) );
02524         }
02525 
02526         $openHead = Html::openElement( 'head' );
02527         if ( $openHead ) {
02528             # Don't bother with the newline if $head == ''
02529             $ret .= "$openHead\n";
02530         }
02531 
02532         if ( !Html::isXmlMimeType( $wgMimeType ) ) {
02533             // Add <meta charset="UTF-8">
02534             // This should be before <title> since it defines the charset used by
02535             // text including the text inside <title>.
02536             // The spec recommends defining XHTML5's charset using the XML declaration
02537             // instead of meta.
02538             // Our XML declaration is output by Html::htmlHeader.
02539             // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type
02540             // http://www.whatwg.org/html/semantics.html#charset
02541             $ret .= Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
02542         }
02543 
02544         $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
02545 
02546         $ret .= implode( "\n", array(
02547             $this->getHeadLinks(),
02548             $this->buildCssLinks(),
02549             $this->getHeadScripts(),
02550             $this->getHeadItems()
02551         ) );
02552 
02553         $closeHead = Html::closeElement( 'head' );
02554         if ( $closeHead ) {
02555             $ret .= "$closeHead\n";
02556         }
02557 
02558         $bodyClasses = array();
02559         $bodyClasses[] = 'mediawiki';
02560 
02561         # Classes for LTR/RTL directionality support
02562         $bodyClasses[] = $userdir;
02563         $bodyClasses[] = "sitedir-$sitedir";
02564 
02565         if ( $this->getLanguage()->capitalizeAllNouns() ) {
02566             # A <body> class is probably not the best way to do this . . .
02567             $bodyClasses[] = 'capitalize-all-nouns';
02568         }
02569 
02570         $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
02571         $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
02572         $bodyClasses[] = 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
02573 
02574         $bodyAttrs = array();
02575         // While the implode() is not strictly needed, it's used for backwards compatibility
02576         // (this used to be built as a string and hooks likely still expect that).
02577         $bodyAttrs['class'] = implode( ' ', $bodyClasses );
02578 
02579         // Allow skins and extensions to add body attributes they need
02580         $sk->addToBodyAttributes( $this, $bodyAttrs );
02581         wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
02582 
02583         $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
02584 
02585         return $ret;
02586     }
02587 
02593     public function getResourceLoader() {
02594         if ( is_null( $this->mResourceLoader ) ) {
02595             $this->mResourceLoader = new ResourceLoader();
02596         }
02597         return $this->mResourceLoader;
02598     }
02599 
02609     protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
02610         global $wgResourceLoaderUseESI;
02611 
02612         $modules = (array)$modules;
02613 
02614         if ( !count( $modules ) ) {
02615             return '';
02616         }
02617 
02618         if ( count( $modules ) > 1 ) {
02619             // Remove duplicate module requests
02620             $modules = array_unique( $modules );
02621             // Sort module names so requests are more uniform
02622             sort( $modules );
02623 
02624             if ( ResourceLoader::inDebugMode() ) {
02625                 // Recursively call us for every item
02626                 $links = '';
02627                 foreach ( $modules as $name ) {
02628                     $links .= $this->makeResourceLoaderLink( $name, $only, $useESI );
02629                 }
02630                 return $links;
02631             }
02632         }
02633         if ( !is_null( $this->mTarget ) ) {
02634             $extraQuery['target'] = $this->mTarget;
02635         }
02636 
02637         // Create keyed-by-group list of module objects from modules list
02638         $groups = array();
02639         $resourceLoader = $this->getResourceLoader();
02640         foreach ( $modules as $name ) {
02641             $module = $resourceLoader->getModule( $name );
02642             # Check that we're allowed to include this module on this page
02643             if ( !$module
02644                 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS )
02645                     && $only == ResourceLoaderModule::TYPE_SCRIPTS )
02646                 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
02647                     && $only == ResourceLoaderModule::TYPE_STYLES )
02648                 || ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) )
02649             ) {
02650                 continue;
02651             }
02652 
02653             $group = $module->getGroup();
02654             if ( !isset( $groups[$group] ) ) {
02655                 $groups[$group] = array();
02656             }
02657             $groups[$group][$name] = $module;
02658         }
02659 
02660         $links = '';
02661         foreach ( $groups as $group => $grpModules ) {
02662             // Special handling for user-specific groups
02663             $user = null;
02664             if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
02665                 $user = $this->getUser()->getName();
02666             }
02667 
02668             // Create a fake request based on the one we are about to make so modules return
02669             // correct timestamp and emptiness data
02670             $query = ResourceLoader::makeLoaderQuery(
02671                 array(), // modules; not determined yet
02672                 $this->getLanguage()->getCode(),
02673                 $this->getSkin()->getSkinName(),
02674                 $user,
02675                 null, // version; not determined yet
02676                 ResourceLoader::inDebugMode(),
02677                 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
02678                 $this->isPrintable(),
02679                 $this->getRequest()->getBool( 'handheld' ),
02680                 $extraQuery
02681             );
02682             $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
02683             // Extract modules that know they're empty
02684             $emptyModules = array();
02685             foreach ( $grpModules as $key => $module ) {
02686                 if ( $module->isKnownEmpty( $context ) ) {
02687                     $emptyModules[$key] = 'ready';
02688                     unset( $grpModules[$key] );
02689                 }
02690             }
02691             // Inline empty modules: since they're empty, just mark them as 'ready'
02692             if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) {
02693                 // If we're only getting the styles, we don't need to do anything for empty modules.
02694                 $links .= Html::inlineScript(
02695                         ResourceLoader::makeLoaderConditionalScript(
02696                                 ResourceLoader::makeLoaderStateScript( $emptyModules )
02697                         )
02698                 ) . "\n";
02699             }
02700 
02701             // If there are no modules left, skip this group
02702             if ( count( $grpModules ) === 0 ) {
02703                 continue;
02704             }
02705 
02706             // Inline private modules. These can't be loaded through load.php for security
02707             // reasons, see bug 34907. Note that these modules should be loaded from
02708             // getHeadScripts() before the first loader call. Otherwise other modules can't
02709             // properly use them as dependencies (bug 30914)
02710             if ( $group === 'private' ) {
02711                 if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
02712                     $links .= Html::inlineStyle(
02713                         $resourceLoader->makeModuleResponse( $context, $grpModules )
02714                     );
02715                 } else {
02716                     $links .= Html::inlineScript(
02717                         ResourceLoader::makeLoaderConditionalScript(
02718                             $resourceLoader->makeModuleResponse( $context, $grpModules )
02719                         )
02720                     );
02721                 }
02722                 $links .= "\n";
02723                 continue;
02724             }
02725             // Special handling for the user group; because users might change their stuff
02726             // on-wiki like user pages, or user preferences; we need to find the highest
02727             // timestamp of these user-changeable modules so we can ensure cache misses on change
02728             // This should NOT be done for the site group (bug 27564) because anons get that too
02729             // and we shouldn't be putting timestamps in Squid-cached HTML
02730             $version = null;
02731             if ( $group === 'user' ) {
02732                 // Get the maximum timestamp
02733                 $timestamp = 1;
02734                 foreach ( $grpModules as $module ) {
02735                     $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
02736                 }
02737                 // Add a version parameter so cache will break when things change
02738                 $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
02739             }
02740 
02741             $url = ResourceLoader::makeLoaderURL(
02742                 array_keys( $grpModules ),
02743                 $this->getLanguage()->getCode(),
02744                 $this->getSkin()->getSkinName(),
02745                 $user,
02746                 $version,
02747                 ResourceLoader::inDebugMode(),
02748                 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
02749                 $this->isPrintable(),
02750                 $this->getRequest()->getBool( 'handheld' ),
02751                 $extraQuery
02752             );
02753             if ( $useESI && $wgResourceLoaderUseESI ) {
02754                 $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
02755                 if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
02756                     $link = Html::inlineStyle( $esi );
02757                 } else {
02758                     $link = Html::inlineScript( $esi );
02759                 }
02760             } else {
02761                 // Automatically select style/script elements
02762                 if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
02763                     $link = Html::linkedStyle( $url );
02764                 } elseif ( $loadCall ) {
02765                     $link = Html::inlineScript(
02766                         ResourceLoader::makeLoaderConditionalScript(
02767                             Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
02768                         )
02769                     );
02770                 } else {
02771                     $link = Html::linkedScript( $url );
02772                 }
02773             }
02774 
02775             if ( $group == 'noscript' ) {
02776                 $links .= Html::rawElement( 'noscript', array(), $link ) . "\n";
02777             } else {
02778                 $links .= $link . "\n";
02779             }
02780         }
02781         return $links;
02782     }
02783 
02790     function getHeadScripts() {
02791         global $wgResourceLoaderExperimentalAsyncLoading;
02792 
02793         // Startup - this will immediately load jquery and mediawiki modules
02794         $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
02795 
02796         // Load config before anything else
02797         $scripts .= Html::inlineScript(
02798             ResourceLoader::makeLoaderConditionalScript(
02799                 ResourceLoader::makeConfigSetScript( $this->getJSVars() )
02800             )
02801         );
02802 
02803         // Load embeddable private modules before any loader links
02804         // This needs to be TYPE_COMBINED so these modules are properly wrapped
02805         // in mw.loader.implement() calls and deferred until mw.user is available
02806         $embedScripts = array( 'user.options', 'user.tokens' );
02807         $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
02808 
02809         // Script and Messages "only" requests marked for top inclusion
02810         // Messages should go first
02811         $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
02812         $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS );
02813 
02814         // Modules requests - let the client calculate dependencies and batch requests as it likes
02815         // Only load modules that have marked themselves for loading at the top
02816         $modules = $this->getModules( true, 'top' );
02817         if ( $modules ) {
02818             $scripts .= Html::inlineScript(
02819                 ResourceLoader::makeLoaderConditionalScript(
02820                     Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
02821                 )
02822             );
02823         }
02824 
02825         if ( $wgResourceLoaderExperimentalAsyncLoading ) {
02826             $scripts .= $this->getScriptsForBottomQueue( true );
02827         }
02828 
02829         return $scripts;
02830     }
02831 
02841     function getScriptsForBottomQueue( $inHead ) {
02842         global $wgUseSiteJs, $wgAllowUserJs;
02843 
02844         // Script and Messages "only" requests marked for bottom inclusion
02845         // If we're in the <head>, use load() calls rather than <script src="..."> tags
02846         // Messages should go first
02847         $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
02848             ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
02849             /* $loadCall = */ $inHead
02850         );
02851         $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
02852             ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
02853             /* $loadCall = */ $inHead
02854         );
02855 
02856         // Modules requests - let the client calculate dependencies and batch requests as it likes
02857         // Only load modules that have marked themselves for loading at the bottom
02858         $modules = $this->getModules( true, 'bottom' );
02859         if ( $modules ) {
02860             $scripts .= Html::inlineScript(
02861                 ResourceLoader::makeLoaderConditionalScript(
02862                     Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
02863                 )
02864             );
02865         }
02866 
02867         // Legacy Scripts
02868         $scripts .= "\n" . $this->mScripts;
02869 
02870         $defaultModules = array();
02871 
02872         // Add site JS if enabled
02873         if ( $wgUseSiteJs ) {
02874             $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
02875                 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02876             );
02877             $defaultModules['site'] = 'loading';
02878         } else {
02879             // Site module is empty, save request by marking ready in advance (bug 46857)
02880             $defaultModules['site'] = 'ready';
02881         }
02882 
02883         // Add user JS if enabled
02884         if ( $wgAllowUserJs ) {
02885             if ( $this->getUser()->isLoggedIn() ) {
02886                 if ( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
02887                     # XXX: additional security check/prompt?
02888                     // We're on a preview of a JS subpage
02889                     // Exclude this page from the user module in case it's in there (bug 26283)
02890                     $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
02891                         array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
02892                     );
02893                     // Load the previewed JS
02894                     $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
02895                     // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
02896                     // asynchronously and may arrive *after* the inline script here. So the previewed code
02897                     // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
02898                 } else {
02899                     // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
02900                     $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
02901                         /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02902                     );
02903                 }
02904                 $defaultModules['user'] = 'loading';
02905             } else {
02906                 // Non-logged-in users have an empty user module.
02907                 // Save request by marking ready in advance (bug 46857)
02908                 $defaultModules['user'] = 'ready';
02909             }
02910         } else {
02911             // User modules are disabled on this wiki.
02912             // Save request by marking ready in advance (bug 46857)
02913             $defaultModules['user'] = 'ready';
02914         }
02915 
02916         // Group JS is only enabled if site JS is enabled.
02917         if ( $wgUseSiteJs ) {
02918             if ( $this->getUser()->isLoggedIn() ) {
02919                 $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
02920                     /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02921                 );
02922                 $defaultModules['user.groups'] = 'loading';
02923             } else {
02924                 // Non-logged-in users have no user.groups module.
02925                 // Save request by marking ready in advance (bug 46857)
02926                 $defaultModules['user.groups'] = 'ready';
02927             }
02928         } else {
02929             // Site (and group JS) disabled
02930             $defaultModules['user.groups'] = 'ready';
02931         }
02932 
02933         $loaderInit = '';
02934         if ( $inHead ) {
02935             // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'.
02936             foreach ( $defaultModules as $m => $state ) {
02937                 if ( $state == 'loading' ) {
02938                     unset( $defaultModules[$m] );
02939                 }
02940             }
02941         }
02942         if ( count( $defaultModules ) > 0 ) {
02943             $loaderInit = Html::inlineScript(
02944                 ResourceLoader::makeLoaderConditionalScript(
02945                     ResourceLoader::makeLoaderStateScript( $defaultModules )
02946                 )
02947             ) . "\n";
02948         }
02949         return $loaderInit . $scripts;
02950     }
02951 
02956     function getBottomScripts() {
02957         global $wgResourceLoaderExperimentalAsyncLoading;
02958 
02959         // Optimise jQuery ready event cross-browser.
02960         // This also enforces $.isReady to be true at </body> which fixes the
02961         // mw.loader bug in Firefox with using document.write between </body>
02962         // and the DOMContentReady event (bug 47457).
02963         $html = Html::inlineScript( 'window.jQuery && jQuery.ready();' );
02964 
02965         if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
02966             $html .= $this->getScriptsForBottomQueue( false );
02967         }
02968 
02969         return $html;
02970     }
02971 
02978     public function addJsConfigVars( $keys, $value = null ) {
02979         if ( is_array( $keys ) ) {
02980             foreach ( $keys as $key => $value ) {
02981                 $this->mJsConfigVars[$key] = $value;
02982             }
02983             return;
02984         }
02985 
02986         $this->mJsConfigVars[$keys] = $value;
02987     }
02988 
03001     public function getJSVars() {
03002         global $wgContLang;
03003 
03004         $curRevisionId = 0;
03005         $articleId = 0;
03006         $canonicalSpecialPageName = false; # bug 21115
03007 
03008         $title = $this->getTitle();
03009         $ns = $title->getNamespace();
03010         $canonicalNamespace = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
03011 
03012         // Get the relevant title so that AJAX features can use the correct page name
03013         // when making API requests from certain special pages (bug 34972).
03014         $relevantTitle = $this->getSkin()->getRelevantTitle();
03015 
03016         if ( $ns == NS_SPECIAL ) {
03017             list( $canonicalSpecialPageName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
03018         } elseif ( $this->canUseWikiPage() ) {
03019             $wikiPage = $this->getWikiPage();
03020             $curRevisionId = $wikiPage->getLatest();
03021             $articleId = $wikiPage->getId();
03022         }
03023 
03024         $lang = $title->getPageLanguage();
03025 
03026         // Pre-process information
03027         $separatorTransTable = $lang->separatorTransformTable();
03028         $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
03029         $compactSeparatorTransTable = array(
03030             implode( "\t", array_keys( $separatorTransTable ) ),
03031             implode( "\t", $separatorTransTable ),
03032         );
03033         $digitTransTable = $lang->digitTransformTable();
03034         $digitTransTable = $digitTransTable ? $digitTransTable : array();
03035         $compactDigitTransTable = array(
03036             implode( "\t", array_keys( $digitTransTable ) ),
03037             implode( "\t", $digitTransTable ),
03038         );
03039 
03040         $user = $this->getUser();
03041 
03042         $vars = array(
03043             'wgCanonicalNamespace' => $canonicalNamespace,
03044             'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
03045             'wgNamespaceNumber' => $title->getNamespace(),
03046             'wgPageName' => $title->getPrefixedDBkey(),
03047             'wgTitle' => $title->getText(),
03048             'wgCurRevisionId' => $curRevisionId,
03049             'wgRevisionId' => (int)$this->getRevisionId(),
03050             'wgArticleId' => $articleId,
03051             'wgIsArticle' => $this->isArticle(),
03052             'wgIsRedirect' => $title->isRedirect(),
03053             'wgAction' => Action::getActionName( $this->getContext() ),
03054             'wgUserName' => $user->isAnon() ? null : $user->getName(),
03055             'wgUserGroups' => $user->getEffectiveGroups(),
03056             'wgCategories' => $this->getCategories(),
03057             'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
03058             'wgPageContentLanguage' => $lang->getCode(),
03059             'wgPageContentModel' => $title->getContentModel(),
03060             'wgSeparatorTransformTable' => $compactSeparatorTransTable,
03061             'wgDigitTransformTable' => $compactDigitTransTable,
03062             'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
03063             'wgMonthNames' => $lang->getMonthNamesArray(),
03064             'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
03065             'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
03066         );
03067         if ( $user->isLoggedIn() ) {
03068             $vars['wgUserId'] = $user->getId();
03069             $vars['wgUserEditCount'] = $user->getEditCount();
03070             $userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
03071             $vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null;
03072             // Get the revision ID of the oldest new message on the user's talk
03073             // page. This can be used for constructing new message alerts on
03074             // the client side.
03075             $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
03076         }
03077         if ( $wgContLang->hasVariants() ) {
03078             $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
03079         }
03080         // Same test as SkinTemplate
03081         $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user ) && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
03082         foreach ( $title->getRestrictionTypes() as $type ) {
03083             $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
03084         }
03085         if ( $title->isMainPage() ) {
03086             $vars['wgIsMainPage'] = true;
03087         }
03088         if ( $this->mRedirectedFrom ) {
03089             $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
03090         }
03091 
03092         // Allow extensions to add their custom variables to the mw.config map.
03093         // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
03094         // page-dependant but site-wide (without state).
03095         // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
03096         wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
03097 
03098         // Merge in variables from addJsConfigVars last
03099         return array_merge( $vars, $this->mJsConfigVars );
03100     }
03101 
03111     public function userCanPreview() {
03112         if ( $this->getRequest()->getVal( 'action' ) != 'submit'
03113             || !$this->getRequest()->wasPosted()
03114             || !$this->getUser()->matchEditToken(
03115                 $this->getRequest()->getVal( 'wpEditToken' ) )
03116         ) {
03117             return false;
03118         }
03119         if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
03120             return false;
03121         }
03122 
03123         return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
03124     }
03125 
03129     public function getHeadLinksArray() {
03130         global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
03131             $wgSitename, $wgVersion,
03132             $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
03133             $wgDisableLangConversion, $wgCanonicalLanguageLinks,
03134             $wgRightsPage, $wgRightsUrl;
03135 
03136         $tags = array();
03137 
03138         $canonicalUrl = $this->mCanonicalUrl;
03139 
03140         $tags['meta-generator'] = Html::element( 'meta', array(
03141             'name' => 'generator',
03142             'content' => "MediaWiki $wgVersion",
03143         ) );
03144 
03145         $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
03146         if ( $p !== 'index,follow' ) {
03147             // http://www.robotstxt.org/wc/meta-user.html
03148             // Only show if it's different from the default robots policy
03149             $tags['meta-robots'] = Html::element( 'meta', array(
03150                 'name' => 'robots',
03151                 'content' => $p,
03152             ) );
03153         }
03154 
03155         foreach ( $this->mMetatags as $tag ) {
03156             if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
03157                 $a = 'http-equiv';
03158                 $tag[0] = substr( $tag[0], 5 );
03159             } else {
03160                 $a = 'name';
03161             }
03162             $tagName = "meta-{$tag[0]}";
03163             if ( isset( $tags[$tagName] ) ) {
03164                 $tagName .= $tag[1];
03165             }
03166             $tags[$tagName] = Html::element( 'meta',
03167                 array(
03168                     $a => $tag[0],
03169                     'content' => $tag[1]
03170                 )
03171             );
03172         }
03173 
03174         foreach ( $this->mLinktags as $tag ) {
03175             $tags[] = Html::element( 'link', $tag );
03176         }
03177 
03178         # Universal edit button
03179         if ( $wgUniversalEditButton && $this->isArticleRelated() ) {
03180             $user = $this->getUser();
03181             if ( $this->getTitle()->quickUserCan( 'edit', $user )
03182                 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
03183                 // Original UniversalEditButton
03184                 $msg = $this->msg( 'edit' )->text();
03185                 $tags['universal-edit-button'] = Html::element( 'link', array(
03186                     'rel' => 'alternate',
03187                     'type' => 'application/x-wiki',
03188                     'title' => $msg,
03189                     'href' => $this->getTitle()->getLocalURL( 'action=edit' )
03190                 ) );
03191                 // Alternate edit link
03192                 $tags['alternative-edit'] = Html::element( 'link', array(
03193                     'rel' => 'edit',
03194                     'title' => $msg,
03195                     'href' => $this->getTitle()->getLocalURL( 'action=edit' )
03196                 ) );
03197             }
03198         }
03199 
03200         # Generally the order of the favicon and apple-touch-icon links
03201         # should not matter, but Konqueror (3.5.9 at least) incorrectly
03202         # uses whichever one appears later in the HTML source. Make sure
03203         # apple-touch-icon is specified first to avoid this.
03204         if ( $wgAppleTouchIcon !== false ) {
03205             $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
03206         }
03207 
03208         if ( $wgFavicon !== false ) {
03209             $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
03210         }
03211 
03212         # OpenSearch description link
03213         $tags['opensearch'] = Html::element( 'link', array(
03214             'rel' => 'search',
03215             'type' => 'application/opensearchdescription+xml',
03216             'href' => wfScript( 'opensearch_desc' ),
03217             'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
03218         ) );
03219 
03220         if ( $wgEnableAPI ) {
03221             # Real Simple Discovery link, provides auto-discovery information
03222             # for the MediaWiki API (and potentially additional custom API
03223             # support such as WordPress or Twitter-compatible APIs for a
03224             # blogging extension, etc)
03225             $tags['rsd'] = Html::element( 'link', array(
03226                 'rel' => 'EditURI',
03227                 'type' => 'application/rsd+xml',
03228                 // Output a protocol-relative URL here if $wgServer is protocol-relative
03229                 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though
03230                 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ),
03231             ) );
03232         }
03233 
03234         # Language variants
03235         if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) {
03236             $lang = $this->getTitle()->getPageLanguage();
03237             if ( $lang->hasVariants() ) {
03238 
03239                 $urlvar = $lang->getURLVariant();
03240 
03241                 if ( !$urlvar ) {
03242                     $variants = $lang->getVariants();
03243                     foreach ( $variants as $_v ) {
03244                         $tags["variant-$_v"] = Html::element( 'link', array(
03245                             'rel' => 'alternate',
03246                             'hreflang' => wfBCP47( $_v ),
03247                             'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
03248                         );
03249                     }
03250                 } else {
03251                     $canonicalUrl = $this->getTitle()->getLocalURL();
03252                 }
03253             }
03254         }
03255 
03256         # Copyright
03257         $copyright = '';
03258         if ( $wgRightsPage ) {
03259             $copy = Title::newFromText( $wgRightsPage );
03260 
03261             if ( $copy ) {
03262                 $copyright = $copy->getLocalURL();
03263             }
03264         }
03265 
03266         if ( !$copyright && $wgRightsUrl ) {
03267             $copyright = $wgRightsUrl;
03268         }
03269 
03270         if ( $copyright ) {
03271             $tags['copyright'] = Html::element( 'link', array(
03272                 'rel' => 'copyright',
03273                 'href' => $copyright )
03274             );
03275         }
03276 
03277         # Feeds
03278         if ( $wgFeed ) {
03279             foreach ( $this->getSyndicationLinks() as $format => $link ) {
03280                 # Use the page name for the title.  In principle, this could
03281                 # lead to issues with having the same name for different feeds
03282                 # corresponding to the same page, but we can't avoid that at
03283                 # this low a level.
03284 
03285                 $tags[] = $this->feedLink(
03286                     $format,
03287                     $link,
03288                     # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
03289                     $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text()
03290                 );
03291             }
03292 
03293             # Recent changes feed should appear on every page (except recentchanges,
03294             # that would be redundant). Put it after the per-page feed to avoid
03295             # changing existing behavior. It's still available, probably via a
03296             # menu in your browser. Some sites might have a different feed they'd
03297             # like to promote instead of the RC feed (maybe like a "Recent New Articles"
03298             # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
03299             # If so, use it instead.
03300             if ( $wgOverrideSiteFeed ) {
03301                 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
03302                     // Note, this->feedLink escapes the url.
03303                     $tags[] = $this->feedLink(
03304                         $type,
03305                         $feedUrl,
03306                         $this->msg( "site-{$type}-feed", $wgSitename )->text()
03307                     );
03308                 }
03309             } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
03310                 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
03311                 foreach ( $wgAdvertisedFeedTypes as $format ) {
03312                     $tags[] = $this->feedLink(
03313                         $format,
03314                         $rctitle->getLocalURL( array( 'feed' => $format ) ),
03315                         $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'.
03316                     );
03317                 }
03318             }
03319         }
03320 
03321         # Canonical URL
03322         global $wgEnableCanonicalServerLink;
03323         if ( $wgEnableCanonicalServerLink ) {
03324             if ( $canonicalUrl !== false ) {
03325                 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
03326             } else {
03327                 $reqUrl = $this->getRequest()->getRequestURL();
03328                 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
03329             }
03330         }
03331         if ( $canonicalUrl !== false ) {
03332             $tags[] = Html::element( 'link', array(
03333                 'rel' => 'canonical',
03334                 'href' => $canonicalUrl
03335             ) );
03336         }
03337 
03338         return $tags;
03339     }
03340 
03344     public function getHeadLinks() {
03345         return implode( "\n", $this->getHeadLinksArray() );
03346     }
03347 
03356     private function feedLink( $type, $url, $text ) {
03357         return Html::element( 'link', array(
03358             'rel' => 'alternate',
03359             'type' => "application/$type+xml",
03360             'title' => $text,
03361             'href' => $url )
03362         );
03363     }
03364 
03374     public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
03375         $options = array();
03376         // Even though we expect the media type to be lowercase, but here we
03377         // force it to lowercase to be safe.
03378         if ( $media ) {
03379             $options['media'] = $media;
03380         }
03381         if ( $condition ) {
03382             $options['condition'] = $condition;
03383         }
03384         if ( $dir ) {
03385             $options['dir'] = $dir;
03386         }
03387         $this->styles[$style] = $options;
03388     }
03389 
03395     public function addInlineStyle( $style_css, $flip = 'noflip' ) {
03396         if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
03397             # If wanted, and the interface is right-to-left, flip the CSS
03398             $style_css = CSSJanus::transform( $style_css, true, false );
03399         }
03400         $this->mInlineStyles .= Html::inlineStyle( $style_css );
03401     }
03402 
03409     public function buildCssLinks() {
03410         global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs, $wgContLang;
03411 
03412         $this->getSkin()->setupSkinUserCss( $this );
03413 
03414         // Add ResourceLoader styles
03415         // Split the styles into four groups
03416         $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() );
03417         $otherTags = ''; // Tags to append after the normal <link> tags
03418         $resourceLoader = $this->getResourceLoader();
03419 
03420         $moduleStyles = $this->getModuleStyles();
03421 
03422         // Per-site custom styles
03423         if ( $wgUseSiteCss ) {
03424             $moduleStyles[] = 'site';
03425             $moduleStyles[] = 'noscript';
03426             if ( $this->getUser()->isLoggedIn() ) {
03427                 $moduleStyles[] = 'user.groups';
03428             }
03429         }
03430 
03431         // Per-user custom styles
03432         if ( $wgAllowUserCss ) {
03433             if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
03434                 // We're on a preview of a CSS subpage
03435                 // Exclude this page from the user module in case it's in there (bug 26283)
03436                 $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
03437                     array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
03438                 );
03439 
03440                 // Load the previewed CSS
03441                 // If needed, Janus it first. This is user-supplied CSS, so it's
03442                 // assumed to be right for the content language directionality.
03443                 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
03444                 if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
03445                     $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
03446                 }
03447                 $otherTags .= Html::inlineStyle( $previewedCSS );
03448             } else {
03449                 // Load the user styles normally
03450                 $moduleStyles[] = 'user';
03451             }
03452         }
03453 
03454         // Per-user preference styles
03455         if ( $wgAllowUserCssPrefs ) {
03456             $moduleStyles[] = 'user.cssprefs';
03457         }
03458 
03459         foreach ( $moduleStyles as $name ) {
03460             $module = $resourceLoader->getModule( $name );
03461             if ( !$module ) {
03462                 continue;
03463             }
03464             $group = $module->getGroup();
03465             // Modules in groups named "other" or anything different than "user", "site" or "private"
03466             // will be placed in the "other" group
03467             $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
03468         }
03469 
03470         // We want site, private and user styles to override dynamically added styles from modules, but we want
03471         // dynamically added styles to override statically added styles from other modules. So the order
03472         // has to be other, dynamic, site, private, user
03473         // Add statically added styles for other modules
03474         $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES );
03475         // Add normal styles added through addStyle()/addInlineStyle() here
03476         $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
03477         // Add marker tag to mark the place where the client-side loader should inject dynamic styles
03478         // We use a <meta> tag with a made-up name for this because that's valid HTML
03479         $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n";
03480 
03481         // Add site, private and user styles
03482         // 'private' at present only contains user.options, so put that before 'user'
03483         // Any future private modules will likely have a similar user-specific character
03484         foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
03485             $ret .= $this->makeResourceLoaderLink( $styles[$group],
03486                     ResourceLoaderModule::TYPE_STYLES
03487             );
03488         }
03489 
03490         // Add stuff in $otherTags (previewed user CSS if applicable)
03491         $ret .= $otherTags;
03492         return $ret;
03493     }
03494 
03498     public function buildCssLinksArray() {
03499         $links = array();
03500 
03501         // Add any extension CSS
03502         foreach ( $this->mExtStyles as $url ) {
03503             $this->addStyle( $url );
03504         }
03505         $this->mExtStyles = array();
03506 
03507         foreach ( $this->styles as $file => $options ) {
03508             $link = $this->styleLink( $file, $options );
03509             if ( $link ) {
03510                 $links[$file] = $link;
03511             }
03512         }
03513         return $links;
03514     }
03515 
03524     protected function styleLink( $style, $options ) {
03525         if ( isset( $options['dir'] ) ) {
03526             if ( $this->getLanguage()->getDir() != $options['dir'] ) {
03527                 return '';
03528             }
03529         }
03530 
03531         if ( isset( $options['media'] ) ) {
03532             $media = self::transformCssMedia( $options['media'] );
03533             if ( is_null( $media ) ) {
03534                 return '';
03535             }
03536         } else {
03537             $media = 'all';
03538         }
03539 
03540         if ( substr( $style, 0, 1 ) == '/' ||
03541             substr( $style, 0, 5 ) == 'http:' ||
03542             substr( $style, 0, 6 ) == 'https:' ) {
03543             $url = $style;
03544         } else {
03545             global $wgStylePath, $wgStyleVersion;
03546             $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
03547         }
03548 
03549         $link = Html::linkedStyle( $url, $media );
03550 
03551         if ( isset( $options['condition'] ) ) {
03552             $condition = htmlspecialchars( $options['condition'] );
03553             $link = "<!--[if $condition]>$link<![endif]-->";
03554         }
03555         return $link;
03556     }
03557 
03565     public static function transformCssMedia( $media ) {
03566         global $wgRequest;
03567 
03568         // http://www.w3.org/TR/css3-mediaqueries/#syntax
03569         $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
03570 
03571         // Switch in on-screen display for media testing
03572         $switches = array(
03573             'printable' => 'print',
03574             'handheld' => 'handheld',
03575         );
03576         foreach ( $switches as $switch => $targetMedia ) {
03577             if ( $wgRequest->getBool( $switch ) ) {
03578                 if ( $media == $targetMedia ) {
03579                     $media = '';
03580                 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
03581                     // This regex will not attempt to understand a comma-separated media_query_list
03582                     //
03583                     // Example supported values for $media: 'screen', 'only screen', 'screen and (min-width: 982px)' ),
03584                     // Example NOT supported value for $media: '3d-glasses, screen, print and resolution > 90dpi'
03585                     //
03586                     // If it's a print request, we never want any kind of screen stylesheets
03587                     // If it's a handheld request (currently the only other choice with a switch),
03588                     // we don't want simple 'screen' but we might want screen queries that
03589                     // have a max-width or something, so we'll pass all others on and let the
03590                     // client do the query.
03591                     if ( $targetMedia == 'print' || $media == 'screen' ) {
03592                         return null;
03593                     }
03594                 }
03595             }
03596         }
03597 
03598         return $media;
03599     }
03600 
03607     public function addWikiMsg( /*...*/ ) {
03608         $args = func_get_args();
03609         $name = array_shift( $args );
03610         $this->addWikiMsgArray( $name, $args );
03611     }
03612 
03621     public function addWikiMsgArray( $name, $args ) {
03622         $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
03623     }
03624 
03648     public function wrapWikiMsg( $wrap /*, ...*/ ) {
03649         $msgSpecs = func_get_args();
03650         array_shift( $msgSpecs );
03651         $msgSpecs = array_values( $msgSpecs );
03652         $s = $wrap;
03653         foreach ( $msgSpecs as $n => $spec ) {
03654             if ( is_array( $spec ) ) {
03655                 $args = $spec;
03656                 $name = array_shift( $args );
03657                 if ( isset( $args['options'] ) ) {
03658                     unset( $args['options'] );
03659                     wfDeprecated(
03660                         'Adding "options" to ' . __METHOD__ . ' is no longer supported',
03661                         '1.20'
03662                     );
03663                 }
03664             } else {
03665                 $args = array();
03666                 $name = $spec;
03667             }
03668             $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
03669         }
03670         $this->addWikiText( $s );
03671     }
03672 
03682     public function includeJQuery( $modules = array() ) {
03683         return array();
03684     }
03685 
03691     public function enableTOC( $flag = true ) {
03692         $this->mEnableTOC = $flag;
03693     }
03694 
03699     public function isTOCEnabled() {
03700         return $this->mEnableTOC;
03701     }
03702 }