MediaWiki  master
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 
00048         var $mExtStyles = array();
00049 
00051         var $mPagetitle = '';
00052 
00054         var $mBodytext = '';
00055 
00061         public $mDebugtext = '';
00062 
00064         var $mHTMLtitle = '';
00065 
00067         var $mIsarticle = false;
00068 
00073         var $mIsArticleRelated = true;
00074 
00079         var $mPrintable = false;
00080 
00087         private $mSubtitle = array();
00088 
00089         var $mRedirect = '';
00090         var $mStatusCode;
00091 
00096         var $mLastModified = '';
00097 
00108         var $mETag = false;
00109 
00110         var $mCategoryLinks = array();
00111         var $mCategories = array();
00112 
00114         var $mLanguageLinks = array();
00115 
00122         var $mScripts = '';
00123 
00127         var $mInlineStyles = '';
00128 
00129         //
00130         var $mLinkColours;
00131 
00136         var $mPageLinkTitle = '';
00137 
00139         var $mHeadItems = array();
00140 
00141         // @todo FIXME: Next variables probably comes from the resource loader
00142         var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array();
00143         var $mResourceLoader;
00144         var $mJsConfigVars = array();
00145 
00147         var $mInlineMsg = array();
00148 
00149         var $mTemplateIds = array();
00150         var $mImageTimeKeys = array();
00151 
00152         var $mRedirectCode = '';
00153 
00154         var $mFeedLinksAppendQuery = null;
00155 
00156         # What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
00157         # @see ResourceLoaderModule::$origin
00158         # ResourceLoaderModule::ORIGIN_ALL is assumed unless overridden;
00159         protected $mAllowedModules = array(
00160                 ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL,
00161         );
00162 
00167         var $mDoNothing = false;
00168 
00169         // Parser related.
00170         var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
00171 
00176         protected $mParserOptions = null;
00177 
00184         var $mFeedLinks = array();
00185 
00186         // Gwicke work on squid caching? Roughly from 2003.
00187         var $mEnableClientCache = true;
00188 
00193         var $mArticleBodyOnly = false;
00194 
00195         var $mNewSectionLink = false;
00196         var $mHideNewSectionLink = false;
00197 
00203         var $mNoGallery = false;
00204 
00205         // should be private.
00206         var $mPageTitleActionText = '';
00207         var $mParseWarnings = array();
00208 
00209         // Cache stuff. Looks like mEnableClientCache
00210         var $mSquidMaxage = 0;
00211 
00212         // @todo document
00213         var $mPreventClickjacking = true;
00214 
00216         var $mRevisionId = null;
00217         private $mRevisionTimestamp = null;
00218 
00219         var $mFileVersion = null;
00220 
00229         var $styles = array();
00230 
00234         protected $mJQueryDone = false;
00235 
00236         private $mIndexPolicy = 'index';
00237         private $mFollowPolicy = 'follow';
00238         private $mVaryHeader = array(
00239                 'Accept-Encoding' => array( 'list-contains=gzip' ),
00240         );
00241 
00248         private $mRedirectedFrom = null;
00249 
00255         function __construct( IContextSource $context = null ) {
00256                 if ( $context === null ) {
00257                         # Extensions should use `new RequestContext` instead of `new OutputPage` now.
00258                         wfDeprecated( __METHOD__, '1.18' );
00259                 } else {
00260                         $this->setContext( $context );
00261                 }
00262         }
00263 
00270         public function redirect( $url, $responsecode = '302' ) {
00271                 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
00272                 $this->mRedirect = str_replace( "\n", '', $url );
00273                 $this->mRedirectCode = $responsecode;
00274         }
00275 
00281         public function getRedirect() {
00282                 return $this->mRedirect;
00283         }
00284 
00290         public function setStatusCode( $statusCode ) {
00291                 $this->mStatusCode = $statusCode;
00292         }
00293 
00301         function addMeta( $name, $val ) {
00302                 array_push( $this->mMetatags, array( $name, $val ) );
00303         }
00304 
00310         function addKeyword( $text ) {
00311                 if( is_array( $text ) ) {
00312                         $this->mKeywords = array_merge( $this->mKeywords, $text );
00313                 } else {
00314                         array_push( $this->mKeywords, $text );
00315                 }
00316         }
00317 
00323         function addLink( $linkarr ) {
00324                 array_push( $this->mLinktags, $linkarr );
00325         }
00326 
00334         function addMetadataLink( $linkarr ) {
00335                 $linkarr['rel'] = $this->getMetadataAttribute();
00336                 $this->addLink( $linkarr );
00337         }
00338 
00344         public function getMetadataAttribute() {
00345                 # note: buggy CC software only reads first "meta" link
00346                 static $haveMeta = false;
00347                 if ( $haveMeta ) {
00348                         return 'alternate meta';
00349                 } else {
00350                         $haveMeta = true;
00351                         return 'meta';
00352                 }
00353         }
00354 
00360         function addScript( $script ) {
00361                 $this->mScripts .= $script . "\n";
00362         }
00363 
00372         public function addExtensionStyle( $url ) {
00373                 array_push( $this->mExtStyles, $url );
00374         }
00375 
00381         function getExtStyle() {
00382                 return $this->mExtStyles;
00383         }
00384 
00392         public function addScriptFile( $file, $version = null ) {
00393                 global $wgStylePath, $wgStyleVersion;
00394                 // See if $file parameter is an absolute URL or begins with a slash
00395                 if( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
00396                         $path = $file;
00397                 } else {
00398                         $path = "{$wgStylePath}/common/{$file}";
00399                 }
00400                 if ( is_null( $version ) )
00401                         $version = $wgStyleVersion;
00402                 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
00403         }
00404 
00410         public function addInlineScript( $script ) {
00411                 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
00412         }
00413 
00419         function getScript() {
00420                 return $this->mScripts . $this->getHeadItems();
00421         }
00422 
00431         protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ){
00432                 $resourceLoader = $this->getResourceLoader();
00433                 $filteredModules = array();
00434                 foreach( $modules as $val ){
00435                         $module = $resourceLoader->getModule( $val );
00436                         if( $module instanceof ResourceLoaderModule
00437                                 && $module->getOrigin() <= $this->getAllowedModules( $type )
00438                                 && ( is_null( $position ) || $module->getPosition() == $position ) )
00439                         {
00440                                 $filteredModules[] = $val;
00441                         }
00442                 }
00443                 return $filteredModules;
00444         }
00445 
00454         public function getModules( $filter = false, $position = null, $param = 'mModules' ) {
00455                 $modules = array_values( array_unique( $this->$param ) );
00456                 return $filter
00457                         ? $this->filterModules( $modules, $position )
00458                         : $modules;
00459         }
00460 
00468         public function addModules( $modules ) {
00469                 $this->mModules = array_merge( $this->mModules, (array)$modules );
00470         }
00471 
00480         public function getModuleScripts( $filter = false, $position = null ) {
00481                 return $this->getModules( $filter, $position, 'mModuleScripts' );
00482         }
00483 
00491         public function addModuleScripts( $modules ) {
00492                 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
00493         }
00494 
00503         public function getModuleStyles( $filter = false, $position = null ) {
00504                 return $this->getModules( $filter,  $position, 'mModuleStyles' );
00505         }
00506 
00514         public function addModuleStyles( $modules ) {
00515                 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
00516         }
00517 
00526         public function getModuleMessages( $filter = false, $position = null ) {
00527                 return $this->getModules( $filter, $position, 'mModuleMessages' );
00528         }
00529 
00537         public function addModuleMessages( $modules ) {
00538                 $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
00539         }
00540 
00546         function getHeadItemsArray() {
00547                 return $this->mHeadItems;
00548         }
00549 
00555         function getHeadItems() {
00556                 $s = '';
00557                 foreach ( $this->mHeadItems as $item ) {
00558                         $s .= $item;
00559                 }
00560                 return $s;
00561         }
00562 
00569         public function addHeadItem( $name, $value ) {
00570                 $this->mHeadItems[$name] = $value;
00571         }
00572 
00579         public function hasHeadItem( $name ) {
00580                 return isset( $this->mHeadItems[$name] );
00581         }
00582 
00588         function setETag( $tag ) {
00589                 $this->mETag = $tag;
00590         }
00591 
00599         public function setArticleBodyOnly( $only ) {
00600                 $this->mArticleBodyOnly = $only;
00601         }
00602 
00608         public function getArticleBodyOnly() {
00609                 return $this->mArticleBodyOnly;
00610         }
00611 
00623         public function checkLastModified( $timestamp ) {
00624                 global $wgCachePages, $wgCacheEpoch;
00625 
00626                 if ( !$timestamp || $timestamp == '19700101000000' ) {
00627                         wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
00628                         return false;
00629                 }
00630                 if( !$wgCachePages ) {
00631                         wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
00632                         return false;
00633                 }
00634                 if( $this->getUser()->getOption( 'nocache' ) ) {
00635                         wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
00636                         return false;
00637                 }
00638 
00639                 $timestamp = wfTimestamp( TS_MW, $timestamp );
00640                 $modifiedTimes = array(
00641                         'page' => $timestamp,
00642                         'user' => $this->getUser()->getTouched(),
00643                         'epoch' => $wgCacheEpoch
00644                 );
00645                 wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
00646 
00647                 $maxModified = max( $modifiedTimes );
00648                 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
00649 
00650                 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
00651                 if ( $clientHeader === false ) {
00652                         wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false );
00653                         return false;
00654                 }
00655 
00656                 # IE sends sizes after the date like this:
00657                 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
00658                 # this breaks strtotime().
00659                 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
00660 
00661                 wfSuppressWarnings(); // E_STRICT system time bitching
00662                 $clientHeaderTime = strtotime( $clientHeader );
00663                 wfRestoreWarnings();
00664                 if ( !$clientHeaderTime ) {
00665                         wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
00666                         return false;
00667                 }
00668                 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
00669 
00670                 # Make debug info
00671                 $info = '';
00672                 foreach ( $modifiedTimes as $name => $value ) {
00673                         if ( $info !== '' ) {
00674                                 $info .= ', ';
00675                         }
00676                         $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
00677                 }
00678 
00679                 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
00680                         wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
00681                 wfDebug( __METHOD__ . ": effective Last-Modified: " .
00682                         wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
00683                 if( $clientHeaderTime < $maxModified ) {
00684                         wfDebug( __METHOD__ . ": STALE, $info\n", false );
00685                         return false;
00686                 }
00687 
00688                 # Not modified
00689                 # Give a 304 response code and disable body output
00690                 wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
00691                 ini_set( 'zlib.output_compression', 0 );
00692                 $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" );
00693                 $this->sendCacheControl();
00694                 $this->disable();
00695 
00696                 // Don't output a compressed blob when using ob_gzhandler;
00697                 // it's technically against HTTP spec and seems to confuse
00698                 // Firefox when the response gets split over two packets.
00699                 wfClearOutputBuffers();
00700 
00701                 return true;
00702         }
00703 
00710         public function setLastModified( $timestamp ) {
00711                 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
00712         }
00713 
00722         public function setRobotPolicy( $policy ) {
00723                 $policy = Article::formatRobotPolicy( $policy );
00724 
00725                 if( isset( $policy['index'] ) ) {
00726                         $this->setIndexPolicy( $policy['index'] );
00727                 }
00728                 if( isset( $policy['follow'] ) ) {
00729                         $this->setFollowPolicy( $policy['follow'] );
00730                 }
00731         }
00732 
00740         public function setIndexPolicy( $policy ) {
00741                 $policy = trim( $policy );
00742                 if( in_array( $policy, array( 'index', 'noindex' ) ) ) {
00743                         $this->mIndexPolicy = $policy;
00744                 }
00745         }
00746 
00754         public function setFollowPolicy( $policy ) {
00755                 $policy = trim( $policy );
00756                 if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
00757                         $this->mFollowPolicy = $policy;
00758                 }
00759         }
00760 
00767         public function setPageTitleActionText( $text ) {
00768                 $this->mPageTitleActionText = $text;
00769         }
00770 
00776         public function getPageTitleActionText() {
00777                 if ( isset( $this->mPageTitleActionText ) ) {
00778                         return $this->mPageTitleActionText;
00779                 }
00780         }
00781 
00788         public function setHTMLTitle( $name ) {
00789                 if ( $name instanceof Message ) {
00790                         $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
00791                 } else {
00792                         $this->mHTMLtitle = $name;
00793                 }
00794         }
00795 
00801         public function getHTMLTitle() {
00802                 return $this->mHTMLtitle;
00803         }
00804 
00810         public function setRedirectedFrom( $t ) {
00811                 $this->mRedirectedFrom = $t;
00812         }
00813 
00822         public function setPageTitle( $name ) {
00823                 if ( $name instanceof Message ) {
00824                         $name = $name->setContext( $this->getContext() )->text();
00825                 }
00826 
00827                 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
00828                 # but leave "<i>foobar</i>" alone
00829                 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
00830                 $this->mPagetitle = $nameWithTags;
00831 
00832                 # change "<i>foo&amp;bar</i>" to "foo&bar"
00833                 $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) );
00834         }
00835 
00841         public function getPageTitle() {
00842                 return $this->mPagetitle;
00843         }
00844 
00850         public function setTitle( Title $t ) {
00851                 $this->getContext()->setTitle( $t );
00852         }
00853 
00859         public function setSubtitle( $str ) {
00860                 $this->clearSubtitle();
00861                 $this->addSubtitle( $str );
00862         }
00863 
00870         public function appendSubtitle( $str ) {
00871                 $this->addSubtitle( $str );
00872         }
00873 
00879         public function addSubtitle( $str ) {
00880                 if ( $str instanceof Message ) {
00881                         $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
00882                 } else {
00883                         $this->mSubtitle[] = $str;
00884                 }
00885         }
00886 
00892         public function addBacklinkSubtitle( Title $title ) {
00893                 $query = array();
00894                 if ( $title->isRedirect() ) {
00895                         $query['redirect'] = 'no';
00896                 }
00897                 $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) );
00898         }
00899 
00903         public function clearSubtitle() {
00904                 $this->mSubtitle = array();
00905         }
00906 
00912         public function getSubtitle() {
00913                 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
00914         }
00915 
00920         public function setPrintable() {
00921                 $this->mPrintable = true;
00922         }
00923 
00929         public function isPrintable() {
00930                 return $this->mPrintable;
00931         }
00932 
00936         public function disable() {
00937                 $this->mDoNothing = true;
00938         }
00939 
00945         public function isDisabled() {
00946                 return $this->mDoNothing;
00947         }
00948 
00954         public function showNewSectionLink() {
00955                 return $this->mNewSectionLink;
00956         }
00957 
00963         public function forceHideNewSectionLink() {
00964                 return $this->mHideNewSectionLink;
00965         }
00966 
00975         public function setSyndicated( $show = true ) {
00976                 if ( $show ) {
00977                         $this->setFeedAppendQuery( false );
00978                 } else {
00979                         $this->mFeedLinks = array();
00980                 }
00981         }
00982 
00992         public function setFeedAppendQuery( $val ) {
00993                 global $wgAdvertisedFeedTypes;
00994 
00995                 $this->mFeedLinks = array();
00996 
00997                 foreach ( $wgAdvertisedFeedTypes as $type ) {
00998                         $query = "feed=$type";
00999                         if ( is_string( $val ) ) {
01000                                 $query .= '&' . $val;
01001                         }
01002                         $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
01003                 }
01004         }
01005 
01012         public function addFeedLink( $format, $href ) {
01013                 global $wgAdvertisedFeedTypes;
01014 
01015                 if ( in_array( $format, $wgAdvertisedFeedTypes ) ) {
01016                         $this->mFeedLinks[$format] = $href;
01017                 }
01018         }
01019 
01024         public function isSyndicated() {
01025                 return count( $this->mFeedLinks ) > 0;
01026         }
01027 
01032         public function getSyndicationLinks() {
01033                 return $this->mFeedLinks;
01034         }
01035 
01041         public function getFeedAppendQuery() {
01042                 return $this->mFeedLinksAppendQuery;
01043         }
01044 
01052         public function setArticleFlag( $v ) {
01053                 $this->mIsarticle = $v;
01054                 if ( $v ) {
01055                         $this->mIsArticleRelated = $v;
01056                 }
01057         }
01058 
01065         public function isArticle() {
01066                 return $this->mIsarticle;
01067         }
01068 
01075         public function setArticleRelated( $v ) {
01076                 $this->mIsArticleRelated = $v;
01077                 if ( !$v ) {
01078                         $this->mIsarticle = false;
01079                 }
01080         }
01081 
01087         public function isArticleRelated() {
01088                 return $this->mIsArticleRelated;
01089         }
01090 
01097         public function addLanguageLinks( $newLinkArray ) {
01098                 $this->mLanguageLinks += $newLinkArray;
01099         }
01100 
01107         public function setLanguageLinks( $newLinkArray ) {
01108                 $this->mLanguageLinks = $newLinkArray;
01109         }
01110 
01116         public function getLanguageLinks() {
01117                 return $this->mLanguageLinks;
01118         }
01119 
01125         public function addCategoryLinks( $categories ) {
01126                 global $wgContLang;
01127 
01128                 if ( !is_array( $categories ) || count( $categories ) == 0 ) {
01129                         return;
01130                 }
01131 
01132                 # Add the links to a LinkBatch
01133                 $arr = array( NS_CATEGORY => $categories );
01134                 $lb = new LinkBatch;
01135                 $lb->setArray( $arr );
01136 
01137                 # Fetch existence plus the hiddencat property
01138                 $dbr = wfGetDB( DB_SLAVE );
01139                 $res = $dbr->select( array( 'page', 'page_props' ),
01140                         array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ),
01141                         $lb->constructSet( 'page', $dbr ),
01142                         __METHOD__,
01143                         array(),
01144                         array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) )
01145                 );
01146 
01147                 # Add the results to the link cache
01148                 $lb->addResultToCache( LinkCache::singleton(), $res );
01149 
01150                 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
01151                 $categories = array_combine(
01152                         array_keys( $categories ),
01153                         array_fill( 0, count( $categories ), 'normal' )
01154                 );
01155 
01156                 # Mark hidden categories
01157                 foreach ( $res as $row ) {
01158                         if ( isset( $row->pp_value ) ) {
01159                                 $categories[$row->page_title] = 'hidden';
01160                         }
01161                 }
01162 
01163                 # Add the remaining categories to the skin
01164                 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
01165                         foreach ( $categories as $category => $type ) {
01166                                 $origcategory = $category;
01167                                 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
01168                                 $wgContLang->findVariantLink( $category, $title, true );
01169                                 if ( $category != $origcategory ) {
01170                                         if ( array_key_exists( $category, $categories ) ) {
01171                                                 continue;
01172                                         }
01173                                 }
01174                                 $text = $wgContLang->convertHtml( $title->getText() );
01175                                 $this->mCategories[] = $title->getText();
01176                                 $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
01177                         }
01178                 }
01179         }
01180 
01186         public function setCategoryLinks( $categories ) {
01187                 $this->mCategoryLinks = array();
01188                 $this->addCategoryLinks( $categories );
01189         }
01190 
01199         public function getCategoryLinks() {
01200                 return $this->mCategoryLinks;
01201         }
01202 
01208         public function getCategories() {
01209                 return $this->mCategories;
01210         }
01211 
01216         public function disallowUserJs() {
01217                 $this->reduceAllowedModules(
01218                         ResourceLoaderModule::TYPE_SCRIPTS,
01219                         ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
01220                 );
01221         }
01222 
01230         public function isUserJsAllowed() {
01231                 wfDeprecated( __METHOD__, '1.18' );
01232                 return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
01233         }
01234 
01241         public function getAllowedModules( $type ){
01242                 if( $type == ResourceLoaderModule::TYPE_COMBINED ){
01243                         return min( array_values( $this->mAllowedModules ) );
01244                 } else {
01245                         return isset( $this->mAllowedModules[$type] )
01246                                 ? $this->mAllowedModules[$type]
01247                                 : ResourceLoaderModule::ORIGIN_ALL;
01248                 }
01249         }
01250 
01256         public function setAllowedModules( $type, $level ){
01257                 $this->mAllowedModules[$type] = $level;
01258         }
01259 
01265         public function reduceAllowedModules( $type, $level ){
01266                 $this->mAllowedModules[$type] = min( $this->getAllowedModules($type), $level );
01267         }
01268 
01274         public function prependHTML( $text ) {
01275                 $this->mBodytext = $text . $this->mBodytext;
01276         }
01277 
01283         public function addHTML( $text ) {
01284                 $this->mBodytext .= $text;
01285         }
01286 
01296         public function addElement( $element, $attribs = array(), $contents = '' ) {
01297                 $this->addHTML( Html::element( $element, $attribs, $contents ) );
01298         }
01299 
01303         public function clearHTML() {
01304                 $this->mBodytext = '';
01305         }
01306 
01312         public function getHTML() {
01313                 return $this->mBodytext;
01314         }
01315 
01323         public function parserOptions( $options = null ) {
01324                 if ( !$this->mParserOptions ) {
01325                         $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
01326                         $this->mParserOptions->setEditSection( false );
01327                 }
01328                 return wfSetVar( $this->mParserOptions, $options );
01329         }
01330 
01338         public function setRevisionId( $revid ) {
01339                 $val = is_null( $revid ) ? null : intval( $revid );
01340                 return wfSetVar( $this->mRevisionId, $val );
01341         }
01342 
01348         public function getRevisionId() {
01349                 return $this->mRevisionId;
01350         }
01351 
01359         public function setRevisionTimestamp( $timestamp) {
01360                 return wfSetVar( $this->mRevisionTimestamp, $timestamp );
01361         }
01362 
01369         public function getRevisionTimestamp() {
01370                 return $this->mRevisionTimestamp;
01371         }
01372 
01379         public function setFileVersion( $file ) {
01380                 $val = null;
01381                 if ( $file instanceof File && $file->exists() ) {
01382                         $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() );
01383                 }
01384                 return wfSetVar( $this->mFileVersion, $val, true );
01385         }
01386 
01392         public function getFileVersion() {
01393                 return $this->mFileVersion;
01394         }
01395 
01402         public function getTemplateIds() {
01403                 return $this->mTemplateIds;
01404         }
01405 
01412         public function getFileSearchOptions() {
01413                 return $this->mImageTimeKeys;
01414         }
01415 
01424         public function addWikiText( $text, $linestart = true, $interface = true ) {
01425                 $title = $this->getTitle(); // Work arround E_STRICT
01426                 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
01427         }
01428 
01436         public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
01437                 $this->addWikiTextTitle( $text, $title, $linestart );
01438         }
01439 
01447         function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
01448                 $this->addWikiTextTitle( $text, $title, $linestart, true );
01449         }
01450 
01457         public function addWikiTextTidy( $text, $linestart = true ) {
01458                 $title = $this->getTitle();
01459                 $this->addWikiTextTitleTidy( $text, $title, $linestart );
01460         }
01461 
01472         public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false, $interface = false ) {
01473                 global $wgParser;
01474 
01475                 wfProfileIn( __METHOD__ );
01476 
01477                 $popts = $this->parserOptions();
01478                 $oldTidy = $popts->setTidy( $tidy );
01479                 $popts->setInterfaceMessage( (bool) $interface );
01480 
01481                 $parserOutput = $wgParser->parse(
01482                         $text, $title, $popts,
01483                         $linestart, true, $this->mRevisionId
01484                 );
01485 
01486                 $popts->setTidy( $oldTidy );
01487 
01488                 $this->addParserOutput( $parserOutput );
01489 
01490                 wfProfileOut( __METHOD__ );
01491         }
01492 
01498         public function addParserOutputNoText( &$parserOutput ) {
01499                 $this->mLanguageLinks += $parserOutput->getLanguageLinks();
01500                 $this->addCategoryLinks( $parserOutput->getCategories() );
01501                 $this->mNewSectionLink = $parserOutput->getNewSection();
01502                 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
01503 
01504                 $this->mParseWarnings = $parserOutput->getWarnings();
01505                 if ( !$parserOutput->isCacheable() ) {
01506                         $this->enableClientCache( false );
01507                 }
01508                 $this->mNoGallery = $parserOutput->getNoGallery();
01509                 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
01510                 $this->addModules( $parserOutput->getModules() );
01511                 $this->addModuleScripts( $parserOutput->getModuleScripts() );
01512                 $this->addModuleStyles( $parserOutput->getModuleStyles() );
01513                 $this->addModuleMessages( $parserOutput->getModuleMessages() );
01514 
01515                 // Template versioning...
01516                 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
01517                         if ( isset( $this->mTemplateIds[$ns] ) ) {
01518                                 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
01519                         } else {
01520                                 $this->mTemplateIds[$ns] = $dbks;
01521                         }
01522                 }
01523                 // File versioning...
01524                 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
01525                         $this->mImageTimeKeys[$dbk] = $data;
01526                 }
01527 
01528                 // Hooks registered in the object
01529                 global $wgParserOutputHooks;
01530                 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
01531                         list( $hookName, $data ) = $hookInfo;
01532                         if ( isset( $wgParserOutputHooks[$hookName] ) ) {
01533                                 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data );
01534                         }
01535                 }
01536 
01537                 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
01538         }
01539 
01545         function addParserOutput( &$parserOutput ) {
01546                 $this->addParserOutputNoText( $parserOutput );
01547                 $text = $parserOutput->getText();
01548                 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
01549                 $this->addHTML( $text );
01550         }
01551 
01552 
01558         public function addTemplate( &$template ) {
01559                 ob_start();
01560                 $template->execute();
01561                 $this->addHTML( ob_get_contents() );
01562                 ob_end_clean();
01563         }
01564 
01579         public function parse( $text, $linestart = true, $interface = false, $language = null ) {
01580                 global $wgParser;
01581 
01582                 if( is_null( $this->getTitle() ) ) {
01583                         throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
01584                 }
01585 
01586                 $popts = $this->parserOptions();
01587                 if ( $interface ) {
01588                         $popts->setInterfaceMessage( true );
01589                 }
01590                 if ( $language !== null ) {
01591                         $oldLang = $popts->setTargetLanguage( $language );
01592                 }
01593 
01594                 $parserOutput = $wgParser->parse(
01595                         $text, $this->getTitle(), $popts,
01596                         $linestart, true, $this->mRevisionId
01597                 );
01598 
01599                 if ( $interface ) {
01600                         $popts->setInterfaceMessage( false );
01601                 }
01602                 if ( $language !== null ) {
01603                         $popts->setTargetLanguage( $oldLang );
01604                 }
01605 
01606                 return $parserOutput->getText();
01607         }
01608 
01619         public function parseInline( $text, $linestart = true, $interface = false ) {
01620                 $parsed = $this->parse( $text, $linestart, $interface );
01621 
01622                 $m = array();
01623                 if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) {
01624                         $parsed = $m[1];
01625                 }
01626 
01627                 return $parsed;
01628         }
01629 
01635         public function setSquidMaxage( $maxage ) {
01636                 $this->mSquidMaxage = $maxage;
01637         }
01638 
01646         public function enableClientCache( $state ) {
01647                 return wfSetVar( $this->mEnableClientCache, $state );
01648         }
01649 
01655         function getCacheVaryCookies() {
01656                 global $wgCookiePrefix, $wgCacheVaryCookies;
01657                 static $cookies;
01658                 if ( $cookies === null ) {
01659                         $cookies = array_merge(
01660                                 array(
01661                                         "{$wgCookiePrefix}Token",
01662                                         "{$wgCookiePrefix}LoggedOut",
01663                                         session_name()
01664                                 ),
01665                                 $wgCacheVaryCookies
01666                         );
01667                         wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) );
01668                 }
01669                 return $cookies;
01670         }
01671 
01678         function haveCacheVaryCookies() {
01679                 $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
01680                 if ( $cookieHeader === false ) {
01681                         return false;
01682                 }
01683                 $cvCookies = $this->getCacheVaryCookies();
01684                 foreach ( $cvCookies as $cookieName ) {
01685                         # Check for a simple string match, like the way squid does it
01686                         if ( strpos( $cookieHeader, $cookieName ) !== false ) {
01687                                 wfDebug( __METHOD__ . ": found $cookieName\n" );
01688                                 return true;
01689                         }
01690                 }
01691                 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
01692                 return false;
01693         }
01694 
01703         public function addVaryHeader( $header, $option = null ) {
01704                 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
01705                         $this->mVaryHeader[$header] = (array)$option;
01706                 } elseif( is_array( $option ) ) {
01707                         if( is_array( $this->mVaryHeader[$header] ) ) {
01708                                 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
01709                         } else {
01710                                 $this->mVaryHeader[$header] = $option;
01711                         }
01712                 }
01713                 $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] );
01714         }
01715 
01722         public function getVaryHeader() {
01723                 return 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) );
01724         }
01725 
01731         public function getXVO() {
01732                 $cvCookies = $this->getCacheVaryCookies();
01733 
01734                 $cookiesOption = array();
01735                 foreach ( $cvCookies as $cookieName ) {
01736                         $cookiesOption[] = 'string-contains=' . $cookieName;
01737                 }
01738                 $this->addVaryHeader( 'Cookie', $cookiesOption );
01739 
01740                 $headers = array();
01741                 foreach( $this->mVaryHeader as $header => $option ) {
01742                         $newheader = $header;
01743                         if ( is_array( $option ) && count( $option ) > 0 ) {
01744                                 $newheader .= ';' . implode( ';', $option );
01745                         }
01746                         $headers[] = $newheader;
01747                 }
01748                 $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
01749 
01750                 return $xvo;
01751         }
01752 
01761         function addAcceptLanguage() {
01762                 $lang = $this->getTitle()->getPageLanguage();
01763                 if( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
01764                         $variants = $lang->getVariants();
01765                         $aloption = array();
01766                         foreach ( $variants as $variant ) {
01767                                 if( $variant === $lang->getCode() ) {
01768                                         continue;
01769                                 } else {
01770                                         $aloption[] = 'string-contains=' . $variant;
01771 
01772                                         // IE and some other browsers use another form of language code
01773                                         // in their Accept-Language header, like "zh-CN" or "zh-TW".
01774                                         // We should handle these too.
01775                                         $ievariant = explode( '-', $variant );
01776                                         if ( count( $ievariant ) == 2 ) {
01777                                                 $ievariant[1] = strtoupper( $ievariant[1] );
01778                                                 $ievariant = implode( '-', $ievariant );
01779                                                 $aloption[] = 'string-contains=' . $ievariant;
01780                                         }
01781                                 }
01782                         }
01783                         $this->addVaryHeader( 'Accept-Language', $aloption );
01784                 }
01785         }
01786 
01797         public function preventClickjacking( $enable = true ) {
01798                 $this->mPreventClickjacking = $enable;
01799         }
01800 
01806         public function allowClickjacking() {
01807                 $this->mPreventClickjacking = false;
01808         }
01809 
01817         public function getFrameOptions() {
01818                 global $wgBreakFrames, $wgEditPageFrameOptions;
01819                 if ( $wgBreakFrames ) {
01820                         return 'DENY';
01821                 } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) {
01822                         return $wgEditPageFrameOptions;
01823                 }
01824                 return false;
01825         }
01826 
01830         public function sendCacheControl() {
01831                 global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO;
01832 
01833                 $response = $this->getRequest()->response();
01834                 if ( $wgUseETag && $this->mETag ) {
01835                         $response->header( "ETag: $this->mETag" );
01836                 }
01837 
01838                 $this->addVaryHeader( 'Cookie' );
01839                 $this->addAcceptLanguage();
01840 
01841                 # don't serve compressed data to clients who can't handle it
01842                 # maintain different caches for logged-in users and non-logged in ones
01843                 $response->header( $this->getVaryHeader() );
01844 
01845                 if ( $wgUseXVO ) {
01846                         # Add an X-Vary-Options header for Squid with Wikimedia patches
01847                         $response->header( $this->getXVO() );
01848                 }
01849 
01850                 if( $this->mEnableClientCache ) {
01851                         if(
01852                                 $wgUseSquid && session_id() == '' && !$this->isPrintable() &&
01853                                 $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies()
01854                         )
01855                         {
01856                                 if ( $wgUseESI ) {
01857                                         # We'll purge the proxy cache explicitly, but require end user agents
01858                                         # to revalidate against the proxy on each visit.
01859                                         # Surrogate-Control controls our Squid, Cache-Control downstream caches
01860                                         wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false );
01861                                         # start with a shorter timeout for initial testing
01862                                         # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
01863                                         $response->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
01864                                         $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
01865                                 } else {
01866                                         # We'll purge the proxy cache for anons explicitly, but require end user agents
01867                                         # to revalidate against the proxy on each visit.
01868                                         # IMPORTANT! The Squid needs to replace the Cache-Control header with
01869                                         # Cache-Control: s-maxage=0, must-revalidate, max-age=0
01870                                         wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false );
01871                                         # start with a shorter timeout for initial testing
01872                                         # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
01873                                         $response->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
01874                                 }
01875                         } else {
01876                                 # We do want clients to cache if they can, but they *must* check for updates
01877                                 # on revisiting the page.
01878                                 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false );
01879                                 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
01880                                 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
01881                         }
01882                         if($this->mLastModified) {
01883                                 $response->header( "Last-Modified: {$this->mLastModified}" );
01884                         }
01885                 } else {
01886                         wfDebug( __METHOD__ . ": no caching **\n", false );
01887 
01888                         # In general, the absence of a last modified header should be enough to prevent
01889                         # the client from using its cache. We send a few other things just to make sure.
01890                         $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
01891                         $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
01892                         $response->header( 'Pragma: no-cache' );
01893                 }
01894         }
01895 
01905         public static function getStatusMessage( $code ) {
01906                 wfDeprecated( __METHOD__, '1.18' );
01907                 return HttpStatus::getMessage( $code );
01908         }
01909 
01914         public function output() {
01915                 global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP;
01916 
01917                 if( $this->mDoNothing ) {
01918                         return;
01919                 }
01920 
01921                 wfProfileIn( __METHOD__ );
01922 
01923                 $response = $this->getRequest()->response();
01924 
01925                 if ( $this->mRedirect != '' ) {
01926                         # Standards require redirect URLs to be absolute
01927                         $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
01928 
01929                         $redirect = $this->mRedirect;
01930                         $code = $this->mRedirectCode;
01931 
01932                         if( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
01933                                 if( $code == '301' || $code == '303' ) {
01934                                         if( !$wgDebugRedirects ) {
01935                                                 $message = HttpStatus::getMessage( $code );
01936                                                 $response->header( "HTTP/1.1 $code $message" );
01937                                         }
01938                                         $this->mLastModified = wfTimestamp( TS_RFC2822 );
01939                                 }
01940                                 if ( $wgVaryOnXFP ) {
01941                                         $this->addVaryHeader( 'X-Forwarded-Proto' );
01942                                 }
01943                                 $this->sendCacheControl();
01944 
01945                                 $response->header( "Content-Type: text/html; charset=utf-8" );
01946                                 if( $wgDebugRedirects ) {
01947                                         $url = htmlspecialchars( $redirect );
01948                                         print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
01949                                         print "<p>Location: <a href=\"$url\">$url</a></p>\n";
01950                                         print "</body>\n</html>\n";
01951                                 } else {
01952                                         $response->header( 'Location: ' . $redirect );
01953                                 }
01954                         }
01955 
01956                         wfProfileOut( __METHOD__ );
01957                         return;
01958                 } elseif ( $this->mStatusCode ) {
01959                         $message = HttpStatus::getMessage( $this->mStatusCode );
01960                         if ( $message ) {
01961                                 $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
01962                         }
01963                 }
01964 
01965                 # Buffer output; final headers may depend on later processing
01966                 ob_start();
01967 
01968                 $response->header( "Content-type: $wgMimeType; charset=UTF-8" );
01969                 $response->header( 'Content-language: ' . $wgLanguageCode );
01970 
01971                 // Prevent framing, if requested
01972                 $frameOptions = $this->getFrameOptions();
01973                 if ( $frameOptions ) {
01974                         $response->header( "X-Frame-Options: $frameOptions" );
01975                 }
01976 
01977                 if ( $this->mArticleBodyOnly ) {
01978                         $this->out( $this->mBodytext );
01979                 } else {
01980                         $this->addDefaultModules();
01981 
01982                         $sk = $this->getSkin();
01983 
01984                         // Hook that allows last minute changes to the output page, e.g.
01985                         // adding of CSS or Javascript by extensions.
01986                         wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
01987 
01988                         wfProfileIn( 'Output-skin' );
01989                         $sk->outputPage();
01990                         wfProfileOut( 'Output-skin' );
01991                 }
01992 
01993                 // This hook allows last minute changes to final overall output by modifying output buffer
01994                 wfRunHooks( 'AfterFinalPageOutput', array( $this ) );
01995 
01996                 $this->sendCacheControl();
01997 
01998                 ob_end_flush();
01999 
02000                 wfProfileOut( __METHOD__ );
02001         }
02002 
02008         public function out( $ins ) {
02009                 print $ins;
02010         }
02011 
02016         function blockedPage() {
02017                 throw new UserBlockedError( $this->getUser()->mBlock );
02018         }
02019 
02030         public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
02031                 $this->setPageTitle( $pageTitle );
02032                 if ( $htmlTitle !== false ) {
02033                         $this->setHTMLTitle( $htmlTitle );
02034                 }
02035                 $this->setRobotPolicy( 'noindex,nofollow' );
02036                 $this->setArticleRelated( false );
02037                 $this->enableClientCache( false );
02038                 $this->mRedirect = '';
02039                 $this->clearSubtitle();
02040                 $this->clearHTML();
02041         }
02042 
02054         public function showErrorPage( $title, $msg, $params = array() ) {
02055                 if( !$title instanceof Message ) {
02056                         $title = $this->msg( $title );
02057                 }
02058 
02059                 $this->prepareErrorPage( $title );
02060 
02061                 if ( $msg instanceof Message ){
02062                         $this->addHTML( $msg->parseAsBlock() );
02063                 } else {
02064                         $this->addWikiMsgArray( $msg, $params );
02065                 }
02066 
02067                 $this->returnToMain();
02068         }
02069 
02076         public function showPermissionsErrorPage( $errors, $action = null ) {
02077                 // For some action (read, edit, create and upload), display a "login to do this action"
02078                 // error if all of the following conditions are met:
02079                 // 1. the user is not logged in
02080                 // 2. the only error is insufficient permissions (i.e. no block or something else)
02081                 // 3. the error can be avoided simply by logging in
02082                 if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) )
02083                         && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
02084                         && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
02085                         && ( User::groupHasPermission( 'user', $action )
02086                         || User::groupHasPermission( 'autoconfirmed', $action ) )
02087                 ) {
02088                         $displayReturnto = null;
02089 
02090                         # Due to bug 32276, if a user does not have read permissions,
02091                         # $this->getTitle() will just give Special:Badtitle, which is
02092                         # not especially useful as a returnto parameter. Use the title
02093                         # from the request instead, if there was one.
02094                         $request = $this->getRequest();
02095                         $returnto = Title::newFromURL( $request->getVal( 'title', '' ) );
02096                         if ( $action == 'edit' ) {
02097                                 $msg = 'whitelistedittext';
02098                                 $displayReturnto = $returnto;
02099                         } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
02100                                 $msg = 'nocreatetext';
02101                         } elseif ( $action == 'upload' ) {
02102                                 $msg = 'uploadnologintext';
02103                         } else { # Read
02104                                 $msg = 'loginreqpagetext';
02105                                 $displayReturnto = Title::newMainPage();
02106                         }
02107 
02108                         $query = array();
02109 
02110                         if ( $returnto ) {
02111                                 $query['returnto'] = $returnto->getPrefixedText();
02112 
02113                                 if ( !$request->wasPosted() ) {
02114                                         $returntoquery = $request->getValues();
02115                                         unset( $returntoquery['title'] );
02116                                         unset( $returntoquery['returnto'] );
02117                                         unset( $returntoquery['returntoquery'] );
02118                                         $query['returntoquery'] = wfArrayToCGI( $returntoquery );
02119                                 }
02120                         }
02121                         $loginLink = Linker::linkKnown(
02122                                 SpecialPage::getTitleFor( 'Userlogin' ),
02123                                 $this->msg( 'loginreqlink' )->escaped(),
02124                                 array(),
02125                                 $query
02126                         );
02127 
02128                         $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
02129                         $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
02130 
02131                         # Don't return to a page the user can't read otherwise
02132                         # we'll end up in a pointless loop
02133                         if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
02134                                 $this->returnToMain( null, $displayReturnto );
02135                         }
02136                 } else {
02137                         $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
02138                         $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
02139                 }
02140         }
02141 
02148         public function versionRequired( $version ) {
02149                 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
02150 
02151                 $this->addWikiMsg( 'versionrequiredtext', $version );
02152                 $this->returnToMain();
02153         }
02154 
02161         public function permissionRequired( $permission ) {
02162                 throw new PermissionsError( $permission );
02163         }
02164 
02170         public function loginToUse() {
02171                 throw new PermissionsError( 'read' );
02172         }
02173 
02181         public function formatPermissionsErrorMessage( $errors, $action = null ) {
02182                 if ( $action == null ) {
02183                         $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
02184                 } else {
02185                         $action_desc = $this->msg( "action-$action" )->plain();
02186                         $text = $this->msg(
02187                                 'permissionserrorstext-withaction',
02188                                 count( $errors ),
02189                                 $action_desc
02190                         )->plain() . "\n\n";
02191                 }
02192 
02193                 if ( count( $errors ) > 1 ) {
02194                         $text .= '<ul class="permissions-errors">' . "\n";
02195 
02196                         foreach( $errors as $error ) {
02197                                 $text .= '<li>';
02198                                 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain();
02199                                 $text .= "</li>\n";
02200                         }
02201                         $text .= '</ul>';
02202                 } else {
02203                         $text .= "<div class=\"permissions-errors\">\n" .
02204                                         call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() .
02205                                         "\n</div>";
02206                 }
02207 
02208                 return $text;
02209         }
02210 
02232         public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
02233                 $this->setRobotPolicy( 'noindex,nofollow' );
02234                 $this->setArticleRelated( false );
02235 
02236                 // If no reason is given, just supply a default "I can't let you do
02237                 // that, Dave" message.  Should only occur if called by legacy code.
02238                 if ( $protected && empty( $reasons ) ) {
02239                         $reasons[] = array( 'badaccess-group0' );
02240                 }
02241 
02242                 if ( !empty( $reasons ) ) {
02243                         // Permissions error
02244                         if( $source ) {
02245                                 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) );
02246                                 $this->addBacklinkSubtitle( $this->getTitle() );
02247                         } else {
02248                                 $this->setPageTitle( $this->msg( 'badaccess' ) );
02249                         }
02250                         $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
02251                 } else {
02252                         // Wiki is read only
02253                         throw new ReadOnlyError;
02254                 }
02255 
02256                 // Show source, if supplied
02257                 if( is_string( $source ) ) {
02258                         $this->addWikiMsg( 'viewsourcetext' );
02259 
02260                         $pageLang = $this->getTitle()->getPageLanguage();
02261                         $params = array(
02262                                 'id'   => 'wpTextbox1',
02263                                 'name' => 'wpTextbox1',
02264                                 'cols' => $this->getUser()->getOption( 'cols' ),
02265                                 'rows' => $this->getUser()->getOption( 'rows' ),
02266                                 'readonly' => 'readonly',
02267                                 'lang' => $pageLang->getHtmlCode(),
02268                                 'dir' => $pageLang->getDir(),
02269                         );
02270                         $this->addHTML( Html::element( 'textarea', $params, $source ) );
02271 
02272                         // Show templates used by this article
02273                         $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() );
02274                         $this->addHTML( "<div class='templatesUsed'>
02275 $templates
02276 </div>
02277 " );
02278                 }
02279 
02280                 # If the title doesn't exist, it's fairly pointless to print a return
02281                 # link to it.  After all, you just tried editing it and couldn't, so
02282                 # what's there to do there?
02283                 if( $this->getTitle()->exists() ) {
02284                         $this->returnToMain( null, $this->getTitle() );
02285                 }
02286         }
02287 
02292         public function rateLimited() {
02293                 throw new ThrottledError;
02294         }
02295 
02305         public function showLagWarning( $lag ) {
02306                 global $wgSlaveLagWarning, $wgSlaveLagCritical;
02307                 if( $lag >= $wgSlaveLagWarning ) {
02308                         $message = $lag < $wgSlaveLagCritical
02309                                 ? 'lag-warn-normal'
02310                                 : 'lag-warn-high';
02311                         $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
02312                         $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) );
02313                 }
02314         }
02315 
02316         public function showFatalError( $message ) {
02317                 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
02318 
02319                 $this->addHTML( $message );
02320         }
02321 
02322         public function showUnexpectedValueError( $name, $val ) {
02323                 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
02324         }
02325 
02326         public function showFileCopyError( $old, $new ) {
02327                 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
02328         }
02329 
02330         public function showFileRenameError( $old, $new ) {
02331                 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
02332         }
02333 
02334         public function showFileDeleteError( $name ) {
02335                 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
02336         }
02337 
02338         public function showFileNotFoundError( $name ) {
02339                 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
02340         }
02341 
02350         public function addReturnTo( $title, $query = array(), $text = null, $options = array() ) {
02351                 if( in_array( 'http', $options ) ) {
02352                         $proto = PROTO_HTTP;
02353                 } elseif( in_array( 'https', $options ) ) {
02354                         $proto = PROTO_HTTPS;
02355                 } else {
02356                         $proto = PROTO_RELATIVE;
02357                 }
02358 
02359                 $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL( '', false, $proto ) ) );
02360                 $link = $this->msg( 'returnto' )->rawParams(
02361                         Linker::link( $title, $text, array(), $query, $options ) )->escaped();
02362                 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
02363         }
02364 
02373         public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
02374                 if ( $returnto == null ) {
02375                         $returnto = $this->getRequest()->getText( 'returnto' );
02376                 }
02377 
02378                 if ( $returntoquery == null ) {
02379                         $returntoquery = $this->getRequest()->getText( 'returntoquery' );
02380                 }
02381 
02382                 if ( $returnto === '' ) {
02383                         $returnto = Title::newMainPage();
02384                 }
02385 
02386                 if ( is_object( $returnto ) ) {
02387                         $titleObj = $returnto;
02388                 } else {
02389                         $titleObj = Title::newFromText( $returnto );
02390                 }
02391                 if ( !is_object( $titleObj ) ) {
02392                         $titleObj = Title::newMainPage();
02393                 }
02394 
02395                 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
02396         }
02397 
02403         public function headElement( Skin $sk, $includeStyle = true ) {
02404                 global $wgContLang;
02405 
02406                 $userdir = $this->getLanguage()->getDir();
02407                 $sitedir = $wgContLang->getDir();
02408 
02409                 if ( $sk->commonPrintStylesheet() ) {
02410                         $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
02411                 }
02412 
02413                 $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
02414 
02415                 if ( $this->getHTMLTitle() == '' ) {
02416                         $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) );
02417                 }
02418 
02419                 $openHead = Html::openElement( 'head' );
02420                 if ( $openHead ) {
02421                         # Don't bother with the newline if $head == ''
02422                         $ret .= "$openHead\n";
02423                 }
02424 
02425                 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
02426 
02427                 $ret .= implode( "\n", array(
02428                         $this->getHeadLinks( null, true ),
02429                         $this->buildCssLinks(),
02430                         $this->getHeadScripts(),
02431                         $this->getHeadItems()
02432                 ) );
02433 
02434                 $closeHead = Html::closeElement( 'head' );
02435                 if ( $closeHead ) {
02436                         $ret .= "$closeHead\n";
02437                 }
02438 
02439                 $bodyAttrs = array();
02440 
02441                 # Classes for LTR/RTL directionality support
02442                 $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir";
02443 
02444                 if ( $this->getLanguage()->capitalizeAllNouns() ) {
02445                         # A <body> class is probably not the best way to do this . . .
02446                         $bodyAttrs['class'] .= ' capitalize-all-nouns';
02447                 }
02448                 $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() );
02449                 $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
02450                 $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
02451 
02452                 $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need
02453                 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
02454 
02455                 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
02456 
02457                 return $ret;
02458         }
02459 
02463         private function addDefaultModules() {
02464                 global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
02465                         $wgAjaxWatch, $wgResponsiveImages;
02466 
02467                 // Add base resources
02468                 $this->addModules( array(
02469                         'mediawiki.user',
02470                         'mediawiki.page.startup',
02471                         'mediawiki.page.ready',
02472                 ) );
02473                 if ( $wgIncludeLegacyJavaScript ){
02474                         $this->addModules( 'mediawiki.legacy.wikibits' );
02475                 }
02476 
02477                 if ( $wgPreloadJavaScriptMwUtil ) {
02478                         $this->addModules( 'mediawiki.util' );
02479                 }
02480 
02481                 MWDebug::addModules( $this );
02482 
02483                 // Add various resources if required
02484                 if ( $wgUseAjax ) {
02485                         $this->addModules( 'mediawiki.legacy.ajax' );
02486 
02487                         wfRunHooks( 'AjaxAddScript', array( &$this ) );
02488 
02489                         if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) {
02490                                 $this->addModules( 'mediawiki.page.watch.ajax' );
02491                         }
02492 
02493                         if ( !$this->getUser()->getOption( 'disablesuggest', false ) ) {
02494                                 $this->addModules( 'mediawiki.searchSuggest' );
02495                         }
02496                 }
02497 
02498                 if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) {
02499                         $this->addModules( 'mediawiki.action.view.rightClickEdit' );
02500                 }
02501 
02502                 # Crazy edit-on-double-click stuff
02503                 if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) {
02504                         $this->addModules( 'mediawiki.action.view.dblClickEdit' );
02505                 }
02506 
02507                 // Support for high-density display images
02508                 if ( $wgResponsiveImages ) {
02509                         $this->addModules( 'mediawiki.hidpi' );
02510                 }
02511         }
02512 
02518         public function getResourceLoader() {
02519                 if ( is_null( $this->mResourceLoader ) ) {
02520                         $this->mResourceLoader = new ResourceLoader();
02521                 }
02522                 return $this->mResourceLoader;
02523         }
02524 
02534         protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
02535                 global $wgResourceLoaderUseESI;
02536 
02537                 $modules = (array) $modules;
02538 
02539                 if ( !count( $modules ) ) {
02540                         return '';
02541                 }
02542 
02543                 if ( count( $modules ) > 1 ) {
02544                         // Remove duplicate module requests
02545                         $modules = array_unique( $modules );
02546                         // Sort module names so requests are more uniform
02547                         sort( $modules );
02548 
02549                         if ( ResourceLoader::inDebugMode() ) {
02550                                 // Recursively call us for every item
02551                                 $links = '';
02552                                 foreach ( $modules as $name ) {
02553                                         $links .= $this->makeResourceLoaderLink( $name, $only, $useESI );
02554                                 }
02555                                 return $links;
02556                         }
02557                 }
02558 
02559                 // Create keyed-by-group list of module objects from modules list
02560                 $groups = array();
02561                 $resourceLoader = $this->getResourceLoader();
02562                 foreach ( $modules as $name ) {
02563                         $module = $resourceLoader->getModule( $name );
02564                         # Check that we're allowed to include this module on this page
02565                         if ( !$module
02566                                 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS )
02567                                         && $only == ResourceLoaderModule::TYPE_SCRIPTS )
02568                                 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
02569                                         && $only == ResourceLoaderModule::TYPE_STYLES )
02570                                 )
02571                         {
02572                                 continue;
02573                         }
02574 
02575                         $group = $module->getGroup();
02576                         if ( !isset( $groups[$group] ) ) {
02577                                 $groups[$group] = array();
02578                         }
02579                         $groups[$group][$name] = $module;
02580                 }
02581 
02582                 $links = '';
02583                 foreach ( $groups as $group => $grpModules ) {
02584                         // Special handling for user-specific groups
02585                         $user = null;
02586                         if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
02587                                 $user = $this->getUser()->getName();
02588                         }
02589 
02590                         // Create a fake request based on the one we are about to make so modules return
02591                         // correct timestamp and emptiness data
02592                         $query = ResourceLoader::makeLoaderQuery(
02593                                 array(), // modules; not determined yet
02594                                 $this->getLanguage()->getCode(),
02595                                 $this->getSkin()->getSkinName(),
02596                                 $user,
02597                                 null, // version; not determined yet
02598                                 ResourceLoader::inDebugMode(),
02599                                 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
02600                                 $this->isPrintable(),
02601                                 $this->getRequest()->getBool( 'handheld' ),
02602                                 $extraQuery
02603                         );
02604                         $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
02605                         // Extract modules that know they're empty
02606                         $emptyModules = array ();
02607                         foreach ( $grpModules as $key => $module ) {
02608                                 if ( $module->isKnownEmpty( $context ) ) {
02609                                         $emptyModules[$key] = 'ready';
02610                                         unset( $grpModules[$key] );
02611                                 }
02612                         }
02613                         // Inline empty modules: since they're empty, just mark them as 'ready'
02614                         if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) {
02615                                 // If we're only getting the styles, we don't need to do anything for empty modules.
02616                                 $links .= Html::inlineScript(
02617 
02618                                                 ResourceLoader::makeLoaderConditionalScript(
02619 
02620                                                                 ResourceLoader::makeLoaderStateScript( $emptyModules )
02621 
02622                                                 )
02623 
02624                                 ) . "\n";
02625                         }
02626 
02627                         // If there are no modules left, skip this group
02628                         if ( count( $grpModules ) === 0 ) {
02629                                 continue;
02630                         }
02631 
02632                         // Inline private modules. These can't be loaded through load.php for security
02633                         // reasons, see bug 34907. Note that these modules should be loaded from
02634                         // getHeadScripts() before the first loader call. Otherwise other modules can't
02635                         // properly use them as dependencies (bug 30914)
02636                         if ( $group === 'private' ) {
02637                                 if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
02638                                         $links .= Html::inlineStyle(
02639                                                 $resourceLoader->makeModuleResponse( $context, $grpModules )
02640                                         );
02641                                 } else {
02642                                         $links .= Html::inlineScript(
02643                                                 ResourceLoader::makeLoaderConditionalScript(
02644                                                         $resourceLoader->makeModuleResponse( $context, $grpModules )
02645                                                 )
02646                                         );
02647                                 }
02648                                 $links .= "\n";
02649                                 continue;
02650                         }
02651                         // Special handling for the user group; because users might change their stuff
02652                         // on-wiki like user pages, or user preferences; we need to find the highest
02653                         // timestamp of these user-changable modules so we can ensure cache misses on change
02654                         // This should NOT be done for the site group (bug 27564) because anons get that too
02655                         // and we shouldn't be putting timestamps in Squid-cached HTML
02656                         $version = null;
02657                         if ( $group === 'user' ) {
02658                                 // Get the maximum timestamp
02659                                 $timestamp = 1;
02660                                 foreach ( $grpModules as $module ) {
02661                                         $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
02662                                 }
02663                                 // Add a version parameter so cache will break when things change
02664                                 $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
02665                         }
02666 
02667                         $url = ResourceLoader::makeLoaderURL(
02668                                 array_keys( $grpModules ),
02669                                 $this->getLanguage()->getCode(),
02670                                 $this->getSkin()->getSkinName(),
02671                                 $user,
02672                                 $version,
02673                                 ResourceLoader::inDebugMode(),
02674                                 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
02675                                 $this->isPrintable(),
02676                                 $this->getRequest()->getBool( 'handheld' ),
02677                                 $extraQuery
02678                         );
02679                         if ( $useESI && $wgResourceLoaderUseESI ) {
02680                                 $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
02681                                 if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
02682                                         $link = Html::inlineStyle( $esi );
02683                                 } else {
02684                                         $link = Html::inlineScript( $esi );
02685                                 }
02686                         } else {
02687                                 // Automatically select style/script elements
02688                                 if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
02689                                         $link = Html::linkedStyle( $url );
02690                                 } else if ( $loadCall ) {
02691                                         $link = Html::inlineScript(
02692                                                 ResourceLoader::makeLoaderConditionalScript(
02693                                                         Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
02694                                                 )
02695                                         );
02696                                 } else {
02697                                         $link = Html::linkedScript( $url );
02698                                 }
02699                         }
02700 
02701                         if( $group == 'noscript' ){
02702                                 $links .= Html::rawElement( 'noscript', array(), $link ) . "\n";
02703                         } else {
02704                                 $links .= $link . "\n";
02705                         }
02706                 }
02707                 return $links;
02708         }
02709 
02716         function getHeadScripts() {
02717                 global $wgResourceLoaderExperimentalAsyncLoading;
02718 
02719                 // Startup - this will immediately load jquery and mediawiki modules
02720                 $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
02721 
02722                 // Load config before anything else
02723                 $scripts .= Html::inlineScript(
02724                         ResourceLoader::makeLoaderConditionalScript(
02725                                 ResourceLoader::makeConfigSetScript( $this->getJSVars() )
02726                         )
02727                 );
02728 
02729                 // Load embeddable private modules before any loader links
02730                 // This needs to be TYPE_COMBINED so these modules are properly wrapped
02731                 // in mw.loader.implement() calls and deferred until mw.user is available
02732                 $embedScripts = array( 'user.options', 'user.tokens' );
02733                 $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
02734 
02735                 // Script and Messages "only" requests marked for top inclusion
02736                 // Messages should go first
02737                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
02738                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS );
02739 
02740                 // Modules requests - let the client calculate dependencies and batch requests as it likes
02741                 // Only load modules that have marked themselves for loading at the top
02742                 $modules = $this->getModules( true, 'top' );
02743                 if ( $modules ) {
02744                         $scripts .= Html::inlineScript(
02745                                 ResourceLoader::makeLoaderConditionalScript(
02746                                         Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
02747                                 )
02748                         );
02749                 }
02750 
02751                 if ( $wgResourceLoaderExperimentalAsyncLoading ) {
02752                         $scripts .= $this->getScriptsForBottomQueue( true );
02753                 }
02754 
02755                 return $scripts;
02756         }
02757 
02767         function getScriptsForBottomQueue( $inHead ) {
02768                 global $wgUseSiteJs, $wgAllowUserJs;
02769 
02770                 // Script and Messages "only" requests marked for bottom inclusion
02771                 // If we're in the <head>, use load() calls rather than <script src="..."> tags
02772                 // Messages should go first
02773                 $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
02774                         ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
02775                         /* $loadCall = */ $inHead
02776                 );
02777                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
02778                         ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
02779                         /* $loadCall = */ $inHead
02780                 );
02781 
02782                 // Modules requests - let the client calculate dependencies and batch requests as it likes
02783                 // Only load modules that have marked themselves for loading at the bottom
02784                 $modules = $this->getModules( true, 'bottom' );
02785                 if ( $modules ) {
02786                         $scripts .= Html::inlineScript(
02787                                 ResourceLoader::makeLoaderConditionalScript(
02788                                         Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
02789                                 )
02790                         );
02791                 }
02792 
02793                 // Legacy Scripts
02794                 $scripts .= "\n" . $this->mScripts;
02795 
02796                 $defaultModules = array();
02797 
02798                 // Add site JS if enabled
02799                 if ( $wgUseSiteJs ) {
02800                         $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
02801                                 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02802                         );
02803                         $defaultModules['site'] = 'loading';
02804                 } else {
02805                         // The wiki is configured to not allow a site module.
02806                         $defaultModules['site'] = 'missing';
02807                 }
02808 
02809                 // Add user JS if enabled
02810                 if ( $wgAllowUserJs ) {
02811                         if ( $this->getUser()->isLoggedIn() ) {
02812                                 if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
02813                                         # XXX: additional security check/prompt?
02814                                         // We're on a preview of a JS subpage
02815                                         // Exclude this page from the user module in case it's in there (bug 26283)
02816                                         $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
02817                                                 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
02818                                         );
02819                                         // Load the previewed JS
02820                                         $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
02821                                         // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
02822                                         // asynchronously and may arrive *after* the inline script here. So the previewed code
02823                                         // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
02824                                 } else {
02825                                         // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
02826                                         $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
02827                                                 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02828                                         );
02829                                 }
02830                                 $defaultModules['user'] = 'loading';
02831                         } else {
02832                                 // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid
02833                                 // blocking default gadgets that might depend on it. Although arguably default-enabled
02834                                 // gadgets should not depend on the user module, it's harmless and less error-prone to
02835                                 // handle this case.
02836                                 $defaultModules['user'] = 'ready';
02837                         }
02838                 } else {
02839                         // User JS disabled
02840                         $defaultModules['user'] = 'missing';
02841                 }
02842 
02843                 // Group JS is only enabled if site JS is enabled.
02844                 if ( $wgUseSiteJs ) {
02845                         if ( $this->getUser()->isLoggedIn() ) {
02846                                 $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
02847                                         /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02848                                 );
02849                                 $defaultModules['user.groups'] = 'loading';
02850                         } else {
02851                                 // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to
02852                                 // avoid blocking gadgets that might depend upon the module.
02853                                 $defaultModules['user.groups'] = 'ready';
02854                         }
02855                 } else {
02856                         // Site (and group JS) disabled
02857                         $defaultModules['user.groups'] = 'missing';
02858                 }
02859 
02860                 $loaderInit = '';
02861                 if ( $inHead ) {
02862                         // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'.
02863                         foreach ( $defaultModules as $m => $state ) {
02864                                 if ( $state == 'loading' ) {
02865                                         unset( $defaultModules[$m] );
02866                                 }
02867                         }
02868                 }
02869                 if ( count( $defaultModules ) > 0 ) {
02870                         $loaderInit = Html::inlineScript(
02871                                 ResourceLoader::makeLoaderConditionalScript(
02872                                         ResourceLoader::makeLoaderStateScript( $defaultModules )
02873                                 )
02874                         ) . "\n";
02875                 }
02876                 return $loaderInit . $scripts;
02877         }
02878 
02883         function getBottomScripts() {
02884                 global $wgResourceLoaderExperimentalAsyncLoading;
02885                 if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
02886                         return $this->getScriptsForBottomQueue( false );
02887                 } else {
02888                         return '';
02889                 }
02890         }
02891 
02898         public function addJsConfigVars( $keys, $value = null ) {
02899                 if ( is_array( $keys ) ) {
02900                         foreach ( $keys as $key => $value ) {
02901                                 $this->mJsConfigVars[$key] = $value;
02902                         }
02903                         return;
02904                 }
02905 
02906                 $this->mJsConfigVars[$keys] = $value;
02907         }
02908 
02909 
02922         public function getJSVars() {
02923                 global $wgContLang;
02924 
02925                 $latestRevID = 0;
02926                 $pageID = 0;
02927                 $canonicalName = false; # bug 21115
02928 
02929                 $title = $this->getTitle();
02930                 $ns = $title->getNamespace();
02931                 $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
02932 
02933                 // Get the relevant title so that AJAX features can use the correct page name
02934                 // when making API requests from certain special pages (bug 34972).
02935                 $relevantTitle = $this->getSkin()->getRelevantTitle();
02936 
02937                 if ( $ns == NS_SPECIAL ) {
02938                         list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
02939                 } elseif ( $this->canUseWikiPage() ) {
02940                         $wikiPage = $this->getWikiPage();
02941                         $latestRevID = $wikiPage->getLatest();
02942                         $pageID = $wikiPage->getId();
02943                 }
02944 
02945                 $lang = $title->getPageLanguage();
02946 
02947                 // Pre-process information
02948                 $separatorTransTable = $lang->separatorTransformTable();
02949                 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
02950                 $compactSeparatorTransTable = array(
02951                         implode( "\t", array_keys( $separatorTransTable ) ),
02952                         implode( "\t", $separatorTransTable ),
02953                 );
02954                 $digitTransTable = $lang->digitTransformTable();
02955                 $digitTransTable = $digitTransTable ? $digitTransTable : array();
02956                 $compactDigitTransTable = array(
02957                         implode( "\t", array_keys( $digitTransTable ) ),
02958                         implode( "\t", $digitTransTable ),
02959                 );
02960 
02961                 $vars = array(
02962                         'wgCanonicalNamespace' => $nsname,
02963                         'wgCanonicalSpecialPageName' => $canonicalName,
02964                         'wgNamespaceNumber' => $title->getNamespace(),
02965                         'wgPageName' => $title->getPrefixedDBKey(),
02966                         'wgTitle' => $title->getText(),
02967                         'wgCurRevisionId' => $latestRevID,
02968                         'wgArticleId' => $pageID,
02969                         'wgIsArticle' => $this->isArticle(),
02970                         'wgAction' => Action::getActionName( $this->getContext() ),
02971                         'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(),
02972                         'wgUserGroups' => $this->getUser()->getEffectiveGroups(),
02973                         'wgCategories' => $this->getCategories(),
02974                         'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
02975                         'wgPageContentLanguage' => $lang->getCode(),
02976                         'wgSeparatorTransformTable' => $compactSeparatorTransTable,
02977                         'wgDigitTransformTable' => $compactDigitTransTable,
02978                         'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
02979                         'wgMonthNames' => $lang->getMonthNamesArray(),
02980                         'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
02981                         'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(),
02982                 );
02983                 if ( $wgContLang->hasVariants() ) {
02984                         $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
02985                 }
02986                 foreach ( $title->getRestrictionTypes() as $type ) {
02987                         $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
02988                 }
02989                 if ( $title->isMainPage() ) {
02990                         $vars['wgIsMainPage'] = true;
02991                 }
02992                 if ( $this->mRedirectedFrom ) {
02993                         $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey();
02994                 }
02995 
02996                 // Allow extensions to add their custom variables to the mw.config map.
02997                 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
02998                 // page-dependant but site-wide (without state).
02999                 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
03000                 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
03001 
03002                 // Merge in variables from addJsConfigVars last
03003                 return array_merge( $vars, $this->mJsConfigVars );
03004         }
03005 
03015         public function userCanPreview() {
03016                 if ( $this->getRequest()->getVal( 'action' ) != 'submit'
03017                         || !$this->getRequest()->wasPosted()
03018                         || !$this->getUser()->matchEditToken(
03019                                 $this->getRequest()->getVal( 'wpEditToken' ) )
03020                 ) {
03021                         return false;
03022                 }
03023                 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
03024                         return false;
03025                 }
03026 
03027                 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
03028         }
03029 
03035         public function getHeadLinksArray( $addContentType = false ) {
03036                 global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
03037                         $wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
03038                         $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
03039                         $wgDisableLangConversion, $wgCanonicalLanguageLinks,
03040                         $wgRightsPage, $wgRightsUrl;
03041 
03042                 $tags = array();
03043 
03044                 if ( $addContentType ) {
03045                         if ( $wgHtml5 ) {
03046                                 # More succinct than <meta http-equiv=Content-Type>, has the
03047                                 # same effect
03048                                 $tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
03049                         } else {
03050                                 $tags['meta-content-type'] = Html::element( 'meta', array(
03051                                         'http-equiv' => 'Content-Type',
03052                                         'content' => "$wgMimeType; charset=UTF-8"
03053                                 ) );
03054                                 $tags['meta-content-style-type'] = Html::element( 'meta', array(  // bug 15835
03055                                         'http-equiv' => 'Content-Style-Type',
03056                                         'content' => 'text/css'
03057                                 ) );
03058                         }
03059                 }
03060 
03061                 $tags['meta-generator'] = Html::element( 'meta', array(
03062                         'name' => 'generator',
03063                         'content' => "MediaWiki $wgVersion",
03064                 ) );
03065 
03066                 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
03067                 if( $p !== 'index,follow' ) {
03068                         // http://www.robotstxt.org/wc/meta-user.html
03069                         // Only show if it's different from the default robots policy
03070                         $tags['meta-robots'] = Html::element( 'meta', array(
03071                                 'name' => 'robots',
03072                                 'content' => $p,
03073                         ) );
03074                 }
03075 
03076                 if ( count( $this->mKeywords ) > 0 ) {
03077                         $strip = array(
03078                                 "/<.*?" . ">/" => '',
03079                                 "/_/" => ' '
03080                         );
03081                         $tags['meta-keywords'] = Html::element( 'meta', array(
03082                                 'name' => 'keywords',
03083                                 'content' =>  preg_replace(
03084                                         array_keys( $strip ),
03085                                         array_values( $strip ),
03086                                         implode( ',', $this->mKeywords )
03087                                 )
03088                         ) );
03089                 }
03090 
03091                 foreach ( $this->mMetatags as $tag ) {
03092                         if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
03093                                 $a = 'http-equiv';
03094                                 $tag[0] = substr( $tag[0], 5 );
03095                         } else {
03096                                 $a = 'name';
03097                         }
03098                         $tagName = "meta-{$tag[0]}";
03099                         if ( isset( $tags[$tagName] ) ) {
03100                                 $tagName .= $tag[1];
03101                         }
03102                         $tags[$tagName] = Html::element( 'meta',
03103                                 array(
03104                                         $a => $tag[0],
03105                                         'content' => $tag[1]
03106                                 )
03107                         );
03108                 }
03109 
03110                 foreach ( $this->mLinktags as $tag ) {
03111                         $tags[] = Html::element( 'link', $tag );
03112                 }
03113 
03114                 # Universal edit button
03115                 if ( $wgUniversalEditButton && $this->isArticleRelated() ) {
03116                         $user = $this->getUser();
03117                         if ( $this->getTitle()->quickUserCan( 'edit', $user )
03118                                 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
03119                                 // Original UniversalEditButton
03120                                 $msg = $this->msg( 'edit' )->text();
03121                                 $tags['universal-edit-button'] = Html::element( 'link', array(
03122                                         'rel' => 'alternate',
03123                                         'type' => 'application/x-wiki',
03124                                         'title' => $msg,
03125                                         'href' => $this->getTitle()->getLocalURL( 'action=edit' )
03126                                 ) );
03127                                 // Alternate edit link
03128                                 $tags['alternative-edit'] = Html::element( 'link', array(
03129                                         'rel' => 'edit',
03130                                         'title' => $msg,
03131                                         'href' => $this->getTitle()->getLocalURL( 'action=edit' )
03132                                 ) );
03133                         }
03134                 }
03135 
03136                 # Generally the order of the favicon and apple-touch-icon links
03137                 # should not matter, but Konqueror (3.5.9 at least) incorrectly
03138                 # uses whichever one appears later in the HTML source. Make sure
03139                 # apple-touch-icon is specified first to avoid this.
03140                 if ( $wgAppleTouchIcon !== false ) {
03141                         $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
03142                 }
03143 
03144                 if ( $wgFavicon !== false ) {
03145                         $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
03146                 }
03147 
03148                 # OpenSearch description link
03149                 $tags['opensearch'] = Html::element( 'link', array(
03150                         'rel' => 'search',
03151                         'type' => 'application/opensearchdescription+xml',
03152                         'href' => wfScript( 'opensearch_desc' ),
03153                         'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
03154                 ) );
03155 
03156                 if ( $wgEnableAPI ) {
03157                         # Real Simple Discovery link, provides auto-discovery information
03158                         # for the MediaWiki API (and potentially additional custom API
03159                         # support such as WordPress or Twitter-compatible APIs for a
03160                         # blogging extension, etc)
03161                         $tags['rsd'] = Html::element( 'link', array(
03162                                 'rel' => 'EditURI',
03163                                 'type' => 'application/rsd+xml',
03164                                 // Output a protocol-relative URL here if $wgServer is protocol-relative
03165                                 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though
03166                                 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ),
03167                         ) );
03168                 }
03169 
03170 
03171                 # Language variants
03172                 if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) {
03173                         $lang = $this->getTitle()->getPageLanguage();
03174                         if ( $lang->hasVariants() ) {
03175 
03176                                 $urlvar = $lang->getURLVariant();
03177 
03178                                 if ( !$urlvar ) {
03179                                         $variants = $lang->getVariants();
03180                                         foreach ( $variants as $_v ) {
03181                                                 $tags["variant-$_v"] = Html::element( 'link', array(
03182                                                         'rel' => 'alternate',
03183                                                         'hreflang' => $_v,
03184                                                         'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
03185                                                 );
03186                                         }
03187                                 } else {
03188                                         $tags['canonical'] = Html::element( 'link', array(
03189                                                 'rel' => 'canonical',
03190                                                 'href' => $this->getTitle()->getCanonicalUrl()
03191                                         ) );
03192                                 }
03193                         }
03194                 }
03195 
03196                 # Copyright
03197                 $copyright = '';
03198                 if ( $wgRightsPage ) {
03199                         $copy = Title::newFromText( $wgRightsPage );
03200 
03201                         if ( $copy ) {
03202                                 $copyright = $copy->getLocalURL();
03203                         }
03204                 }
03205 
03206                 if ( !$copyright && $wgRightsUrl ) {
03207                         $copyright = $wgRightsUrl;
03208                 }
03209 
03210                 if ( $copyright ) {
03211                         $tags['copyright'] = Html::element( 'link', array(
03212                                 'rel' => 'copyright',
03213                                 'href' => $copyright )
03214                         );
03215                 }
03216 
03217                 # Feeds
03218                 if ( $wgFeed ) {
03219                         foreach( $this->getSyndicationLinks() as $format => $link ) {
03220                                 # Use the page name for the title.  In principle, this could
03221                                 # lead to issues with having the same name for different feeds
03222                                 # corresponding to the same page, but we can't avoid that at
03223                                 # this low a level.
03224 
03225                                 $tags[] = $this->feedLink(
03226                                         $format,
03227                                         $link,
03228                                         # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
03229                                         $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text()
03230                                 );
03231                         }
03232 
03233                         # Recent changes feed should appear on every page (except recentchanges,
03234                         # that would be redundant). Put it after the per-page feed to avoid
03235                         # changing existing behavior. It's still available, probably via a
03236                         # menu in your browser. Some sites might have a different feed they'd
03237                         # like to promote instead of the RC feed (maybe like a "Recent New Articles"
03238                         # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
03239                         # If so, use it instead.
03240                         if ( $wgOverrideSiteFeed ) {
03241                                 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
03242                                         // Note, this->feedLink escapes the url.
03243                                         $tags[] = $this->feedLink(
03244                                                 $type,
03245                                                 $feedUrl,
03246                                                 $this->msg( "site-{$type}-feed", $wgSitename )->text()
03247                                         );
03248                                 }
03249                         } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
03250                                 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
03251                                 foreach ( $wgAdvertisedFeedTypes as $format ) {
03252                                         $tags[] = $this->feedLink(
03253                                                 $format,
03254                                                 $rctitle->getLocalURL( "feed={$format}" ),
03255                                                 $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'.
03256                                         );
03257                                 }
03258                         }
03259                 }
03260                 return $tags;
03261         }
03262 
03269         public function getHeadLinks( $unused = null, $addContentType = false ) {
03270                 return implode( "\n", $this->getHeadLinksArray( $addContentType ) );
03271         }
03272 
03281         private function feedLink( $type, $url, $text ) {
03282                 return Html::element( 'link', array(
03283                         'rel' => 'alternate',
03284                         'type' => "application/$type+xml",
03285                         'title' => $text,
03286                         'href' => $url )
03287                 );
03288         }
03289 
03299         public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
03300                 $options = array();
03301                 // Even though we expect the media type to be lowercase, but here we
03302                 // force it to lowercase to be safe.
03303                 if( $media ) {
03304                         $options['media'] = $media;
03305                 }
03306                 if( $condition ) {
03307                         $options['condition'] = $condition;
03308                 }
03309                 if( $dir ) {
03310                         $options['dir'] = $dir;
03311                 }
03312                 $this->styles[$style] = $options;
03313         }
03314 
03320         public function addInlineStyle( $style_css, $flip = 'noflip' ) {
03321                 if( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
03322                         # If wanted, and the interface is right-to-left, flip the CSS
03323                         $style_css = CSSJanus::transform( $style_css, true, false );
03324                 }
03325                 $this->mInlineStyles .= Html::inlineStyle( $style_css );
03326         }
03327 
03334         public function buildCssLinks() {
03335                 global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs,
03336                         $wgLang, $wgContLang;
03337 
03338                 $this->getSkin()->setupSkinUserCss( $this );
03339 
03340                 // Add ResourceLoader styles
03341                 // Split the styles into four groups
03342                 $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() );
03343                 $otherTags = ''; // Tags to append after the normal <link> tags
03344                 $resourceLoader = $this->getResourceLoader();
03345 
03346                 $moduleStyles = $this->getModuleStyles();
03347 
03348                 // Per-site custom styles
03349                 if ( $wgUseSiteCss ) {
03350                         $moduleStyles[] = 'site';
03351                         $moduleStyles[] = 'noscript';
03352                         if( $this->getUser()->isLoggedIn() ){
03353                                 $moduleStyles[] = 'user.groups';
03354                         }
03355                 }
03356 
03357                 // Per-user custom styles
03358                 if ( $wgAllowUserCss ) {
03359                         if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
03360                                 // We're on a preview of a CSS subpage
03361                                 // Exclude this page from the user module in case it's in there (bug 26283)
03362                                 $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
03363                                         array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
03364                                 );
03365 
03366                                 // Load the previewed CSS
03367                                 // If needed, Janus it first. This is user-supplied CSS, so it's
03368                                 // assumed to be right for the content language directionality.
03369                                 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
03370                                 if ( $wgLang->getDir() !== $wgContLang->getDir() ) {
03371                                         $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
03372                                 }
03373                                 $otherTags .= Html::inlineStyle( $previewedCSS );
03374                         } else {
03375                                 // Load the user styles normally
03376                                 $moduleStyles[] = 'user';
03377                         }
03378                 }
03379 
03380                 // Per-user preference styles
03381                 if ( $wgAllowUserCssPrefs ) {
03382                         $moduleStyles[] = 'user.cssprefs';
03383                 }
03384 
03385                 foreach ( $moduleStyles as $name ) {
03386                         $module = $resourceLoader->getModule( $name );
03387                         if ( !$module ) {
03388                                 continue;
03389                         }
03390                         $group = $module->getGroup();
03391                         // Modules in groups named "other" or anything different than "user", "site" or "private"
03392                         // will be placed in the "other" group
03393                         $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
03394                 }
03395 
03396                 // We want site, private and user styles to override dynamically added styles from modules, but we want
03397                 // dynamically added styles to override statically added styles from other modules. So the order
03398                 // has to be other, dynamic, site, private, user
03399                 // Add statically added styles for other modules
03400                 $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES );
03401                 // Add normal styles added through addStyle()/addInlineStyle() here
03402                 $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
03403                 // Add marker tag to mark the place where the client-side loader should inject dynamic styles
03404                 // We use a <meta> tag with a made-up name for this because that's valid HTML
03405                 $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n";
03406 
03407                 // Add site, private and user styles
03408                 // 'private' at present only contains user.options, so put that before 'user'
03409                 // Any future private modules will likely have a similar user-specific character
03410                 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
03411                         $ret .= $this->makeResourceLoaderLink( $styles[$group],
03412                                         ResourceLoaderModule::TYPE_STYLES
03413                         );
03414                 }
03415 
03416                 // Add stuff in $otherTags (previewed user CSS if applicable)
03417                 $ret .= $otherTags;
03418                 return $ret;
03419         }
03420 
03424         public function buildCssLinksArray() {
03425                 $links = array();
03426 
03427                 // Add any extension CSS
03428                 foreach ( $this->mExtStyles as $url ) {
03429                         $this->addStyle( $url );
03430                 }
03431                 $this->mExtStyles = array();
03432 
03433                 foreach( $this->styles as $file => $options ) {
03434                         $link = $this->styleLink( $file, $options );
03435                         if( $link ) {
03436                                 $links[$file] = $link;
03437                         }
03438                 }
03439                 return $links;
03440         }
03441 
03450         protected function styleLink( $style, $options ) {
03451                 if( isset( $options['dir'] ) ) {
03452                         if( $this->getLanguage()->getDir() != $options['dir'] ) {
03453                                 return '';
03454                         }
03455                 }
03456 
03457                 if( isset( $options['media'] ) ) {
03458                         $media = self::transformCssMedia( $options['media'] );
03459                         if( is_null( $media ) ) {
03460                                 return '';
03461                         }
03462                 } else {
03463                         $media = 'all';
03464                 }
03465 
03466                 if( substr( $style, 0, 1 ) == '/' ||
03467                         substr( $style, 0, 5 ) == 'http:' ||
03468                         substr( $style, 0, 6 ) == 'https:' ) {
03469                         $url = $style;
03470                 } else {
03471                         global $wgStylePath, $wgStyleVersion;
03472                         $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
03473                 }
03474 
03475                 $link = Html::linkedStyle( $url, $media );
03476 
03477                 if( isset( $options['condition'] ) ) {
03478                         $condition = htmlspecialchars( $options['condition'] );
03479                         $link = "<!--[if $condition]>$link<![endif]-->";
03480                 }
03481                 return $link;
03482         }
03483 
03490         public static function transformCssMedia( $media ) {
03491                 global $wgRequest, $wgHandheldForIPhone;
03492 
03493                 // Switch in on-screen display for media testing
03494                 $switches = array(
03495                         'printable' => 'print',
03496                         'handheld' => 'handheld',
03497                 );
03498                 foreach( $switches as $switch => $targetMedia ) {
03499                         if( $wgRequest->getBool( $switch ) ) {
03500                                 if( $media == $targetMedia ) {
03501                                         $media = '';
03502                                 } elseif( $media == 'screen' ) {
03503                                         return null;
03504                                 }
03505                         }
03506                 }
03507 
03508                 // Expand longer media queries as iPhone doesn't grok 'handheld'
03509                 if( $wgHandheldForIPhone ) {
03510                         $mediaAliases = array(
03511                                 'screen' => 'screen and (min-device-width: 481px)',
03512                                 'handheld' => 'handheld, only screen and (max-device-width: 480px)',
03513                         );
03514 
03515                         if( isset( $mediaAliases[$media] ) ) {
03516                                 $media = $mediaAliases[$media];
03517                         }
03518                 }
03519 
03520                 return $media;
03521         }
03522 
03529         public function addWikiMsg( /*...*/ ) {
03530                 $args = func_get_args();
03531                 $name = array_shift( $args );
03532                 $this->addWikiMsgArray( $name, $args );
03533         }
03534 
03543         public function addWikiMsgArray( $name, $args ) {
03544                 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
03545         }
03546 
03570         public function wrapWikiMsg( $wrap /*, ...*/ ) {
03571                 $msgSpecs = func_get_args();
03572                 array_shift( $msgSpecs );
03573                 $msgSpecs = array_values( $msgSpecs );
03574                 $s = $wrap;
03575                 foreach ( $msgSpecs as $n => $spec ) {
03576                         if ( is_array( $spec ) ) {
03577                                 $args = $spec;
03578                                 $name = array_shift( $args );
03579                                 if ( isset( $args['options'] ) ) {
03580                                         unset( $args['options'] );
03581                                         wfDeprecated(
03582                                                 'Adding "options" to ' . __METHOD__ . ' is no longer supported',
03583                                                 '1.20'
03584                                         );
03585                                 }
03586                         }  else {
03587                                 $args = array();
03588                                 $name = $spec;
03589                         }
03590                         $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
03591                 }
03592                 $this->addWikiText( $s );
03593         }
03594 
03604         public function includeJQuery( $modules = array() ) {
03605                 return array();
03606         }
03607 
03608 }