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