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