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