MediaWiki  REL1_20
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__ );
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 
00854 
00860         public function setSubtitle( $str ) {
00861                 $this->clearSubtitle();
00862                 $this->addSubtitle( $str );
00863         }
00864 
00871         public function appendSubtitle( $str ) {
00872                 $this->addSubtitle( $str );
00873         }
00874 
00880         public function addSubtitle( $str ) {
00881                 if ( $str instanceof Message ) {
00882                         $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
00883                 } else {
00884                         $this->mSubtitle[] = $str;
00885                 }
00886         }
00887 
00893         public function addBacklinkSubtitle( Title $title ) {
00894                 $query = array();
00895                 if ( $title->isRedirect() ) {
00896                         $query['redirect'] = 'no';
00897                 }
00898                 $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) );
00899         }
00900 
00904         public function clearSubtitle() {
00905                 $this->mSubtitle = array();
00906         }
00907 
00913         public function getSubtitle() {
00914                 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
00915         }
00916 
00921         public function setPrintable() {
00922                 $this->mPrintable = true;
00923         }
00924 
00930         public function isPrintable() {
00931                 return $this->mPrintable;
00932         }
00933 
00937         public function disable() {
00938                 $this->mDoNothing = true;
00939         }
00940 
00946         public function isDisabled() {
00947                 return $this->mDoNothing;
00948         }
00949 
00955         public function showNewSectionLink() {
00956                 return $this->mNewSectionLink;
00957         }
00958 
00964         public function forceHideNewSectionLink() {
00965                 return $this->mHideNewSectionLink;
00966         }
00967 
00976         public function setSyndicated( $show = true ) {
00977                 if ( $show ) {
00978                         $this->setFeedAppendQuery( false );
00979                 } else {
00980                         $this->mFeedLinks = array();
00981                 }
00982         }
00983 
00993         public function setFeedAppendQuery( $val ) {
00994                 global $wgAdvertisedFeedTypes;
00995 
00996                 $this->mFeedLinks = array();
00997 
00998                 foreach ( $wgAdvertisedFeedTypes as $type ) {
00999                         $query = "feed=$type";
01000                         if ( is_string( $val ) ) {
01001                                 $query .= '&' . $val;
01002                         }
01003                         $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
01004                 }
01005         }
01006 
01013         public function addFeedLink( $format, $href ) {
01014                 global $wgAdvertisedFeedTypes;
01015 
01016                 if ( in_array( $format, $wgAdvertisedFeedTypes ) ) {
01017                         $this->mFeedLinks[$format] = $href;
01018                 }
01019         }
01020 
01025         public function isSyndicated() {
01026                 return count( $this->mFeedLinks ) > 0;
01027         }
01028 
01033         public function getSyndicationLinks() {
01034                 return $this->mFeedLinks;
01035         }
01036 
01042         public function getFeedAppendQuery() {
01043                 return $this->mFeedLinksAppendQuery;
01044         }
01045 
01053         public function setArticleFlag( $v ) {
01054                 $this->mIsarticle = $v;
01055                 if ( $v ) {
01056                         $this->mIsArticleRelated = $v;
01057                 }
01058         }
01059 
01066         public function isArticle() {
01067                 return $this->mIsarticle;
01068         }
01069 
01076         public function setArticleRelated( $v ) {
01077                 $this->mIsArticleRelated = $v;
01078                 if ( !$v ) {
01079                         $this->mIsarticle = false;
01080                 }
01081         }
01082 
01088         public function isArticleRelated() {
01089                 return $this->mIsArticleRelated;
01090         }
01091 
01098         public function addLanguageLinks( $newLinkArray ) {
01099                 $this->mLanguageLinks += $newLinkArray;
01100         }
01101 
01108         public function setLanguageLinks( $newLinkArray ) {
01109                 $this->mLanguageLinks = $newLinkArray;
01110         }
01111 
01117         public function getLanguageLinks() {
01118                 return $this->mLanguageLinks;
01119         }
01120 
01126         public function addCategoryLinks( $categories ) {
01127                 global $wgContLang;
01128 
01129                 if ( !is_array( $categories ) || count( $categories ) == 0 ) {
01130                         return;
01131                 }
01132 
01133                 # Add the links to a LinkBatch
01134                 $arr = array( NS_CATEGORY => $categories );
01135                 $lb = new LinkBatch;
01136                 $lb->setArray( $arr );
01137 
01138                 # Fetch existence plus the hiddencat property
01139                 $dbr = wfGetDB( DB_SLAVE );
01140                 $res = $dbr->select( array( 'page', 'page_props' ),
01141                         array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ),
01142                         $lb->constructSet( 'page', $dbr ),
01143                         __METHOD__,
01144                         array(),
01145                         array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) )
01146                 );
01147 
01148                 # Add the results to the link cache
01149                 $lb->addResultToCache( LinkCache::singleton(), $res );
01150 
01151                 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
01152                 $categories = array_combine(
01153                         array_keys( $categories ),
01154                         array_fill( 0, count( $categories ), 'normal' )
01155                 );
01156 
01157                 # Mark hidden categories
01158                 foreach ( $res as $row ) {
01159                         if ( isset( $row->pp_value ) ) {
01160                                 $categories[$row->page_title] = 'hidden';
01161                         }
01162                 }
01163 
01164                 # Add the remaining categories to the skin
01165                 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
01166                         foreach ( $categories as $category => $type ) {
01167                                 $origcategory = $category;
01168                                 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
01169                                 $wgContLang->findVariantLink( $category, $title, true );
01170                                 if ( $category != $origcategory ) {
01171                                         if ( array_key_exists( $category, $categories ) ) {
01172                                                 continue;
01173                                         }
01174                                 }
01175                                 $text = $wgContLang->convertHtml( $title->getText() );
01176                                 $this->mCategories[] = $title->getText();
01177                                 $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
01178                         }
01179                 }
01180         }
01181 
01187         public function setCategoryLinks( $categories ) {
01188                 $this->mCategoryLinks = array();
01189                 $this->addCategoryLinks( $categories );
01190         }
01191 
01200         public function getCategoryLinks() {
01201                 return $this->mCategoryLinks;
01202         }
01203 
01209         public function getCategories() {
01210                 return $this->mCategories;
01211         }
01212 
01217         public function disallowUserJs() {
01218                 $this->reduceAllowedModules(
01219                         ResourceLoaderModule::TYPE_SCRIPTS,
01220                         ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
01221                 );
01222         }
01223 
01231         public function isUserJsAllowed() {
01232                 wfDeprecated( __METHOD__, '1.18' );
01233                 return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
01234         }
01235 
01242         public function getAllowedModules( $type ){
01243                 if( $type == ResourceLoaderModule::TYPE_COMBINED ){
01244                         return min( array_values( $this->mAllowedModules ) );
01245                 } else {
01246                         return isset( $this->mAllowedModules[$type] )
01247                                 ? $this->mAllowedModules[$type]
01248                                 : ResourceLoaderModule::ORIGIN_ALL;
01249                 }
01250         }
01251 
01257         public function setAllowedModules( $type, $level ){
01258                 $this->mAllowedModules[$type] = $level;
01259         }
01260 
01266         public function reduceAllowedModules( $type, $level ){
01267                 $this->mAllowedModules[$type] = min( $this->getAllowedModules($type), $level );
01268         }
01269 
01275         public function prependHTML( $text ) {
01276                 $this->mBodytext = $text . $this->mBodytext;
01277         }
01278 
01284         public function addHTML( $text ) {
01285                 $this->mBodytext .= $text;
01286         }
01287 
01297         public function addElement( $element, $attribs = array(), $contents = '' ) {
01298                 $this->addHTML( Html::element( $element, $attribs, $contents ) );
01299         }
01300 
01304         public function clearHTML() {
01305                 $this->mBodytext = '';
01306         }
01307 
01313         public function getHTML() {
01314                 return $this->mBodytext;
01315         }
01316 
01324         public function parserOptions( $options = null ) {
01325                 if ( !$this->mParserOptions ) {
01326                         $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
01327                         $this->mParserOptions->setEditSection( false );
01328                 }
01329                 return wfSetVar( $this->mParserOptions, $options );
01330         }
01331 
01339         public function setRevisionId( $revid ) {
01340                 $val = is_null( $revid ) ? null : intval( $revid );
01341                 return wfSetVar( $this->mRevisionId, $val );
01342         }
01343 
01349         public function getRevisionId() {
01350                 return $this->mRevisionId;
01351         }
01352 
01360         public function setRevisionTimestamp( $timestamp) {
01361                 return wfSetVar( $this->mRevisionTimestamp, $timestamp );
01362         }
01363 
01370         public function getRevisionTimestamp() {
01371                 return $this->mRevisionTimestamp;
01372         }
01373 
01380         public function setFileVersion( $file ) {
01381                 $val = null;
01382                 if ( $file instanceof File && $file->exists() ) {
01383                         $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() );
01384                 }
01385                 return wfSetVar( $this->mFileVersion, $val, true );
01386         }
01387 
01393         public function getFileVersion() {
01394                 return $this->mFileVersion;
01395         }
01396 
01403         public function getTemplateIds() {
01404                 return $this->mTemplateIds;
01405         }
01406 
01413         public function getFileSearchOptions() {
01414                 return $this->mImageTimeKeys;
01415         }
01416 
01425         public function addWikiText( $text, $linestart = true, $interface = true ) {
01426                 $title = $this->getTitle(); // Work arround E_STRICT
01427                 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
01428         }
01429 
01437         public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
01438                 $this->addWikiTextTitle( $text, $title, $linestart );
01439         }
01440 
01448         function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
01449                 $this->addWikiTextTitle( $text, $title, $linestart, true );
01450         }
01451 
01458         public function addWikiTextTidy( $text, $linestart = true ) {
01459                 $title = $this->getTitle();
01460                 $this->addWikiTextTitleTidy( $text, $title, $linestart );
01461         }
01462 
01473         public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false, $interface = false ) {
01474                 global $wgParser;
01475 
01476                 wfProfileIn( __METHOD__ );
01477 
01478                 $popts = $this->parserOptions();
01479                 $oldTidy = $popts->setTidy( $tidy );
01480                 $popts->setInterfaceMessage( (bool) $interface );
01481 
01482                 $parserOutput = $wgParser->parse(
01483                         $text, $title, $popts,
01484                         $linestart, true, $this->mRevisionId
01485                 );
01486 
01487                 $popts->setTidy( $oldTidy );
01488 
01489                 $this->addParserOutput( $parserOutput );
01490 
01491                 wfProfileOut( __METHOD__ );
01492         }
01493 
01499         public function addParserOutputNoText( &$parserOutput ) {
01500                 $this->mLanguageLinks += $parserOutput->getLanguageLinks();
01501                 $this->addCategoryLinks( $parserOutput->getCategories() );
01502                 $this->mNewSectionLink = $parserOutput->getNewSection();
01503                 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
01504 
01505                 $this->mParseWarnings = $parserOutput->getWarnings();
01506                 if ( !$parserOutput->isCacheable() ) {
01507                         $this->enableClientCache( false );
01508                 }
01509                 $this->mNoGallery = $parserOutput->getNoGallery();
01510                 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
01511                 $this->addModules( $parserOutput->getModules() );
01512                 $this->addModuleScripts( $parserOutput->getModuleScripts() );
01513                 $this->addModuleStyles( $parserOutput->getModuleStyles() );
01514                 $this->addModuleMessages( $parserOutput->getModuleMessages() );
01515 
01516                 // Template versioning...
01517                 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
01518                         if ( isset( $this->mTemplateIds[$ns] ) ) {
01519                                 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
01520                         } else {
01521                                 $this->mTemplateIds[$ns] = $dbks;
01522                         }
01523                 }
01524                 // File versioning...
01525                 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
01526                         $this->mImageTimeKeys[$dbk] = $data;
01527                 }
01528 
01529                 // Hooks registered in the object
01530                 global $wgParserOutputHooks;
01531                 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
01532                         list( $hookName, $data ) = $hookInfo;
01533                         if ( isset( $wgParserOutputHooks[$hookName] ) ) {
01534                                 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data );
01535                         }
01536                 }
01537 
01538                 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
01539         }
01540 
01546         function addParserOutput( &$parserOutput ) {
01547                 $this->addParserOutputNoText( $parserOutput );
01548                 $text = $parserOutput->getText();
01549                 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
01550                 $this->addHTML( $text );
01551         }
01552 
01553 
01559         public function addTemplate( &$template ) {
01560                 ob_start();
01561                 $template->execute();
01562                 $this->addHTML( ob_get_contents() );
01563                 ob_end_clean();
01564         }
01565 
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__ );
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                 ob_end_flush();
01998                 wfProfileOut( __METHOD__ );
01999         }
02000 
02006         public function out( $ins ) {
02007                 print $ins;
02008         }
02009 
02014         function blockedPage() {
02015                 throw new UserBlockedError( $this->getUser()->mBlock );
02016         }
02017 
02028         public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
02029                 $this->setPageTitle( $pageTitle );
02030                 if ( $htmlTitle !== false ) {
02031                         $this->setHTMLTitle( $htmlTitle );
02032                 }
02033                 $this->setRobotPolicy( 'noindex,nofollow' );
02034                 $this->setArticleRelated( false );
02035                 $this->enableClientCache( false );
02036                 $this->mRedirect = '';
02037                 $this->clearSubtitle();
02038                 $this->clearHTML();
02039         }
02040 
02052         public function showErrorPage( $title, $msg, $params = array() ) {
02053                 if( !$title instanceof Message ) {
02054                         $title = $this->msg( $title );
02055                 }
02056 
02057                 $this->prepareErrorPage( $title );
02058 
02059                 if ( $msg instanceof Message ){
02060                         $this->addHTML( $msg->parse() );
02061                 } else {
02062                         $this->addWikiMsgArray( $msg, $params );
02063                 }
02064 
02065                 $this->returnToMain();
02066         }
02067 
02074         public function showPermissionsErrorPage( $errors, $action = null ) {
02075                 global $wgGroupPermissions;
02076 
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                         && ( ( isset( $wgGroupPermissions['user'][$action] ) && $wgGroupPermissions['user'][$action] )
02086                         || ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['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 
02160         public function permissionRequired( $permission ) {
02161                 throw new PermissionsError( $permission );
02162         }
02163 
02169         public function loginToUse() {
02170                 throw new PermissionsError( 'read' );
02171         }
02172 
02180         public function formatPermissionsErrorMessage( $errors, $action = null ) {
02181                 if ( $action == null ) {
02182                         $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
02183                 } else {
02184                         $action_desc = $this->msg( "action-$action" )->plain();
02185                         $text = $this->msg(
02186                                 'permissionserrorstext-withaction',
02187                                 count( $errors ),
02188                                 $action_desc
02189                         )->plain() . "\n\n";
02190                 }
02191 
02192                 if ( count( $errors ) > 1 ) {
02193                         $text .= '<ul class="permissions-errors">' . "\n";
02194 
02195                         foreach( $errors as $error ) {
02196                                 $text .= '<li>';
02197                                 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain();
02198                                 $text .= "</li>\n";
02199                         }
02200                         $text .= '</ul>';
02201                 } else {
02202                         $text .= "<div class=\"permissions-errors\">\n" .
02203                                         call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() .
02204                                         "\n</div>";
02205                 }
02206 
02207                 return $text;
02208         }
02209 
02230         public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
02231                 $this->setRobotPolicy( 'noindex,nofollow' );
02232                 $this->setArticleRelated( false );
02233 
02234                 // If no reason is given, just supply a default "I can't let you do
02235                 // that, Dave" message.  Should only occur if called by legacy code.
02236                 if ( $protected && empty( $reasons ) ) {
02237                         $reasons[] = array( 'badaccess-group0' );
02238                 }
02239 
02240                 if ( !empty( $reasons ) ) {
02241                         // Permissions error
02242                         if( $source ) {
02243                                 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) );
02244                                 $this->addBacklinkSubtitle( $this->getTitle() );
02245                         } else {
02246                                 $this->setPageTitle( $this->msg( 'badaccess' ) );
02247                         }
02248                         $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
02249                 } else {
02250                         // Wiki is read only
02251                         throw new ReadOnlyError;
02252                 }
02253 
02254                 // Show source, if supplied
02255                 if( is_string( $source ) ) {
02256                         $this->addWikiMsg( 'viewsourcetext' );
02257 
02258                         $pageLang = $this->getTitle()->getPageLanguage();
02259                         $params = array(
02260                                 'id'   => 'wpTextbox1',
02261                                 'name' => 'wpTextbox1',
02262                                 'cols' => $this->getUser()->getOption( 'cols' ),
02263                                 'rows' => $this->getUser()->getOption( 'rows' ),
02264                                 'readonly' => 'readonly',
02265                                 'lang' => $pageLang->getHtmlCode(),
02266                                 'dir' => $pageLang->getDir(),
02267                         );
02268                         $this->addHTML( Html::element( 'textarea', $params, $source ) );
02269 
02270                         // Show templates used by this article
02271                         $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() );
02272                         $this->addHTML( "<div class='templatesUsed'>
02273 $templates
02274 </div>
02275 " );
02276                 }
02277 
02278                 # If the title doesn't exist, it's fairly pointless to print a return
02279                 # link to it.  After all, you just tried editing it and couldn't, so
02280                 # what's there to do there?
02281                 if( $this->getTitle()->exists() ) {
02282                         $this->returnToMain( null, $this->getTitle() );
02283                 }
02284         }
02285 
02290         public function rateLimited() {
02291                 throw new ThrottledError;
02292         }
02293 
02303         public function showLagWarning( $lag ) {
02304                 global $wgSlaveLagWarning, $wgSlaveLagCritical;
02305                 if( $lag >= $wgSlaveLagWarning ) {
02306                         $message = $lag < $wgSlaveLagCritical
02307                                 ? 'lag-warn-normal'
02308                                 : 'lag-warn-high';
02309                         $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
02310                         $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) );
02311                 }
02312         }
02313 
02314         public function showFatalError( $message ) {
02315                 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
02316 
02317                 $this->addHTML( $message );
02318         }
02319 
02320         public function showUnexpectedValueError( $name, $val ) {
02321                 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
02322         }
02323 
02324         public function showFileCopyError( $old, $new ) {
02325                 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
02326         }
02327 
02328         public function showFileRenameError( $old, $new ) {
02329                 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
02330         }
02331 
02332         public function showFileDeleteError( $name ) {
02333                 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
02334         }
02335 
02336         public function showFileNotFoundError( $name ) {
02337                 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
02338         }
02339 
02347         public function addReturnTo( $title, $query = array(), $text = null ) {
02348                 $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL() ) );
02349                 $link = $this->msg( 'returnto' )->rawParams(
02350                         Linker::link( $title, $text, array(), $query ) )->escaped();
02351                 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
02352         }
02353 
02362         public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
02363                 if ( $returnto == null ) {
02364                         $returnto = $this->getRequest()->getText( 'returnto' );
02365                 }
02366 
02367                 if ( $returntoquery == null ) {
02368                         $returntoquery = $this->getRequest()->getText( 'returntoquery' );
02369                 }
02370 
02371                 if ( $returnto === '' ) {
02372                         $returnto = Title::newMainPage();
02373                 }
02374 
02375                 if ( is_object( $returnto ) ) {
02376                         $titleObj = $returnto;
02377                 } else {
02378                         $titleObj = Title::newFromText( $returnto );
02379                 }
02380                 if ( !is_object( $titleObj ) ) {
02381                         $titleObj = Title::newMainPage();
02382                 }
02383 
02384                 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
02385         }
02386 
02392         public function headElement( Skin $sk, $includeStyle = true ) {
02393                 global $wgContLang;
02394 
02395                 $userdir = $this->getLanguage()->getDir();
02396                 $sitedir = $wgContLang->getDir();
02397 
02398                 if ( $sk->commonPrintStylesheet() ) {
02399                         $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
02400                 }
02401 
02402                 $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
02403 
02404                 if ( $this->getHTMLTitle() == '' ) {
02405                         $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) );
02406                 }
02407 
02408                 $openHead = Html::openElement( 'head' );
02409                 if ( $openHead ) {
02410                         # Don't bother with the newline if $head == ''
02411                         $ret .= "$openHead\n";
02412                 }
02413 
02414                 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
02415 
02416                 $ret .= implode( "\n", array(
02417                         $this->getHeadLinks( null, true ),
02418                         $this->buildCssLinks(),
02419                         $this->getHeadScripts(),
02420                         $this->getHeadItems()
02421                 ) );
02422 
02423                 $closeHead = Html::closeElement( 'head' );
02424                 if ( $closeHead ) {
02425                         $ret .= "$closeHead\n";
02426                 }
02427 
02428                 $bodyAttrs = array();
02429 
02430                 # Classes for LTR/RTL directionality support
02431                 $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir";
02432 
02433                 if ( $this->getLanguage()->capitalizeAllNouns() ) {
02434                         # A <body> class is probably not the best way to do this . . .
02435                         $bodyAttrs['class'] .= ' capitalize-all-nouns';
02436                 }
02437                 $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() );
02438                 $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
02439                 $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
02440 
02441                 $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need
02442                 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
02443 
02444                 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
02445 
02446                 return $ret;
02447         }
02448 
02452         private function addDefaultModules() {
02453                 global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
02454                         $wgAjaxWatch;
02455 
02456                 // Add base resources
02457                 $this->addModules( array(
02458                         'mediawiki.user',
02459                         'mediawiki.page.startup',
02460                         'mediawiki.page.ready',
02461                 ) );
02462                 if ( $wgIncludeLegacyJavaScript ){
02463                         $this->addModules( 'mediawiki.legacy.wikibits' );
02464                 }
02465 
02466                 if ( $wgPreloadJavaScriptMwUtil ) {
02467                         $this->addModules( 'mediawiki.util' );
02468                 }
02469 
02470                 MWDebug::addModules( $this );
02471 
02472                 // Add various resources if required
02473                 if ( $wgUseAjax ) {
02474                         $this->addModules( 'mediawiki.legacy.ajax' );
02475 
02476                         wfRunHooks( 'AjaxAddScript', array( &$this ) );
02477 
02478                         if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) {
02479                                 $this->addModules( 'mediawiki.page.watch.ajax' );
02480                         }
02481 
02482                         if ( !$this->getUser()->getOption( 'disablesuggest', false ) ) {
02483                                 $this->addModules( 'mediawiki.searchSuggest' );
02484                         }
02485                 }
02486 
02487                 if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) {
02488                         $this->addModules( 'mediawiki.action.view.rightClickEdit' );
02489                 }
02490 
02491                 # Crazy edit-on-double-click stuff
02492                 if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) {
02493                         $this->addModules( 'mediawiki.action.view.dblClickEdit' );
02494                 }
02495         }
02496 
02502         public function getResourceLoader() {
02503                 if ( is_null( $this->mResourceLoader ) ) {
02504                         $this->mResourceLoader = new ResourceLoader();
02505                 }
02506                 return $this->mResourceLoader;
02507         }
02508 
02518         protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
02519                 global $wgResourceLoaderUseESI;
02520 
02521                 $modules = (array) $modules;
02522 
02523                 if ( !count( $modules ) ) {
02524                         return '';
02525                 }
02526 
02527                 if ( count( $modules ) > 1 ) {
02528                         // Remove duplicate module requests
02529                         $modules = array_unique( $modules );
02530                         // Sort module names so requests are more uniform
02531                         sort( $modules );
02532 
02533                         if ( ResourceLoader::inDebugMode() ) {
02534                                 // Recursively call us for every item
02535                                 $links = '';
02536                                 foreach ( $modules as $name ) {
02537                                         $links .= $this->makeResourceLoaderLink( $name, $only, $useESI );
02538                                 }
02539                                 return $links;
02540                         }
02541                 }
02542 
02543                 // Create keyed-by-group list of module objects from modules list
02544                 $groups = array();
02545                 $resourceLoader = $this->getResourceLoader();
02546                 foreach ( $modules as $name ) {
02547                         $module = $resourceLoader->getModule( $name );
02548                         # Check that we're allowed to include this module on this page
02549                         if ( !$module
02550                                 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS )
02551                                         && $only == ResourceLoaderModule::TYPE_SCRIPTS )
02552                                 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
02553                                         && $only == ResourceLoaderModule::TYPE_STYLES )
02554                                 )
02555                         {
02556                                 continue;
02557                         }
02558 
02559                         $group = $module->getGroup();
02560                         if ( !isset( $groups[$group] ) ) {
02561                                 $groups[$group] = array();
02562                         }
02563                         $groups[$group][$name] = $module;
02564                 }
02565 
02566                 $links = '';
02567                 foreach ( $groups as $group => $grpModules ) {
02568                         // Special handling for user-specific groups
02569                         $user = null;
02570                         if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
02571                                 $user = $this->getUser()->getName();
02572                         }
02573 
02574                         // Create a fake request based on the one we are about to make so modules return
02575                         // correct timestamp and emptiness data
02576                         $query = ResourceLoader::makeLoaderQuery(
02577                                 array(), // modules; not determined yet
02578                                 $this->getLanguage()->getCode(),
02579                                 $this->getSkin()->getSkinName(),
02580                                 $user,
02581                                 null, // version; not determined yet
02582                                 ResourceLoader::inDebugMode(),
02583                                 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
02584                                 $this->isPrintable(),
02585                                 $this->getRequest()->getBool( 'handheld' ),
02586                                 $extraQuery
02587                         );
02588                         $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
02589                         // Extract modules that know they're empty
02590                         $emptyModules = array ();
02591                         foreach ( $grpModules as $key => $module ) {
02592                                 if ( $module->isKnownEmpty( $context ) ) {
02593                                         $emptyModules[$key] = 'ready';
02594                                         unset( $grpModules[$key] );
02595                                 }
02596                         }
02597                         // Inline empty modules: since they're empty, just mark them as 'ready'
02598                         if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) {
02599                                 // If we're only getting the styles, we don't need to do anything for empty modules.
02600                                 $links .= Html::inlineScript(
02601 
02602                                                 ResourceLoader::makeLoaderConditionalScript(
02603 
02604                                                                 ResourceLoader::makeLoaderStateScript( $emptyModules )
02605 
02606                                                 )
02607 
02608                                 ) . "\n";
02609                         }
02610 
02611                         // If there are no modules left, skip this group
02612                         if ( count( $grpModules ) === 0 ) {
02613                                 continue;
02614                         }
02615 
02616                         // Inline private modules. These can't be loaded through load.php for security
02617                         // reasons, see bug 34907. Note that these modules should be loaded from
02618                         // getHeadScripts() before the first loader call. Otherwise other modules can't
02619                         // properly use them as dependencies (bug 30914)
02620                         if ( $group === 'private' ) {
02621                                 if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
02622                                         $links .= Html::inlineStyle(
02623                                                 $resourceLoader->makeModuleResponse( $context, $grpModules )
02624                                         );
02625                                 } else {
02626                                         $links .= Html::inlineScript(
02627                                                 ResourceLoader::makeLoaderConditionalScript(
02628                                                         $resourceLoader->makeModuleResponse( $context, $grpModules )
02629                                                 )
02630                                         );
02631                                 }
02632                                 $links .= "\n";
02633                                 continue;
02634                         }
02635                         // Special handling for the user group; because users might change their stuff
02636                         // on-wiki like user pages, or user preferences; we need to find the highest
02637                         // timestamp of these user-changable modules so we can ensure cache misses on change
02638                         // This should NOT be done for the site group (bug 27564) because anons get that too
02639                         // and we shouldn't be putting timestamps in Squid-cached HTML
02640                         $version = null;
02641                         if ( $group === 'user' ) {
02642                                 // Get the maximum timestamp
02643                                 $timestamp = 1;
02644                                 foreach ( $grpModules as $module ) {
02645                                         $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
02646                                 }
02647                                 // Add a version parameter so cache will break when things change
02648                                 $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
02649                         }
02650 
02651                         $url = ResourceLoader::makeLoaderURL(
02652                                 array_keys( $grpModules ),
02653                                 $this->getLanguage()->getCode(),
02654                                 $this->getSkin()->getSkinName(),
02655                                 $user,
02656                                 $version,
02657                                 ResourceLoader::inDebugMode(),
02658                                 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
02659                                 $this->isPrintable(),
02660                                 $this->getRequest()->getBool( 'handheld' ),
02661                                 $extraQuery
02662                         );
02663                         if ( $useESI && $wgResourceLoaderUseESI ) {
02664                                 $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
02665                                 if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
02666                                         $link = Html::inlineStyle( $esi );
02667                                 } else {
02668                                         $link = Html::inlineScript( $esi );
02669                                 }
02670                         } else {
02671                                 // Automatically select style/script elements
02672                                 if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
02673                                         $link = Html::linkedStyle( $url );
02674                                 } else if ( $loadCall ) {
02675                                         $link = Html::inlineScript(
02676                                                 ResourceLoader::makeLoaderConditionalScript(
02677                                                         Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
02678                                                 )
02679                                         );
02680                                 } else {
02681                                         $link = Html::linkedScript( $url );
02682                                 }
02683                         }
02684 
02685                         if( $group == 'noscript' ){
02686                                 $links .= Html::rawElement( 'noscript', array(), $link ) . "\n";
02687                         } else {
02688                                 $links .= $link . "\n";
02689                         }
02690                 }
02691                 return $links;
02692         }
02693 
02700         function getHeadScripts() {
02701                 global $wgResourceLoaderExperimentalAsyncLoading;
02702 
02703                 // Startup - this will immediately load jquery and mediawiki modules
02704                 $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
02705 
02706                 // Load config before anything else
02707                 $scripts .= Html::inlineScript(
02708                         ResourceLoader::makeLoaderConditionalScript(
02709                                 ResourceLoader::makeConfigSetScript( $this->getJSVars() )
02710                         )
02711                 );
02712 
02713                 // Load embeddable private modules before any loader links
02714                 // This needs to be TYPE_COMBINED so these modules are properly wrapped
02715                 // in mw.loader.implement() calls and deferred until mw.user is available
02716                 $embedScripts = array( 'user.options', 'user.tokens' );
02717                 $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
02718 
02719                 // Script and Messages "only" requests marked for top inclusion
02720                 // Messages should go first
02721                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
02722                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS );
02723 
02724                 // Modules requests - let the client calculate dependencies and batch requests as it likes
02725                 // Only load modules that have marked themselves for loading at the top
02726                 $modules = $this->getModules( true, 'top' );
02727                 if ( $modules ) {
02728                         $scripts .= Html::inlineScript(
02729                                 ResourceLoader::makeLoaderConditionalScript(
02730                                         Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
02731                                 )
02732                         );
02733                 }
02734 
02735                 if ( $wgResourceLoaderExperimentalAsyncLoading ) {
02736                         $scripts .= $this->getScriptsForBottomQueue( true );
02737                 }
02738 
02739                 return $scripts;
02740         }
02741 
02751         function getScriptsForBottomQueue( $inHead ) {
02752                 global $wgUseSiteJs, $wgAllowUserJs;
02753 
02754                 // Script and Messages "only" requests marked for bottom inclusion
02755                 // If we're in the <head>, use load() calls rather than <script src="..."> tags
02756                 // Messages should go first
02757                 $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
02758                         ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
02759                         /* $loadCall = */ $inHead
02760                 );
02761                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
02762                         ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
02763                         /* $loadCall = */ $inHead
02764                 );
02765 
02766                 // Modules requests - let the client calculate dependencies and batch requests as it likes
02767                 // Only load modules that have marked themselves for loading at the bottom
02768                 $modules = $this->getModules( true, 'bottom' );
02769                 if ( $modules ) {
02770                         $scripts .= Html::inlineScript(
02771                                 ResourceLoader::makeLoaderConditionalScript(
02772                                         Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
02773                                 )
02774                         );
02775                 }
02776 
02777                 // Legacy Scripts
02778                 $scripts .= "\n" . $this->mScripts;
02779 
02780                 $defaultModules = array();
02781 
02782                 // Add site JS if enabled
02783                 if ( $wgUseSiteJs ) {
02784                         $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
02785                                 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02786                         );
02787                         $defaultModules['site'] = 'loading';
02788                 } else {
02789                         // The wiki is configured to not allow a site module.
02790                         $defaultModules['site'] = 'missing';
02791                 }
02792 
02793                 // Add user JS if enabled
02794                 if ( $wgAllowUserJs ) {
02795                         if ( $this->getUser()->isLoggedIn() ) {
02796                                 if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
02797                                         # XXX: additional security check/prompt?
02798                                         // We're on a preview of a JS subpage
02799                                         // Exclude this page from the user module in case it's in there (bug 26283)
02800                                         $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
02801                                                 array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
02802                                         );
02803                                         // Load the previewed JS
02804                                         $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
02805                                         // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
02806                                         // asynchronously and may arrive *after* the inline script here. So the previewed code
02807                                         // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
02808                                 } else {
02809                                         // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
02810                                         $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
02811                                                 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02812                                         );
02813                                 }
02814                                 $defaultModules['user'] = 'loading';
02815                         } else {
02816                                 // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid
02817                                 // blocking default gadgets that might depend on it. Although arguably default-enabled
02818                                 // gadgets should not depend on the user module, it's harmless and less error-prone to
02819                                 // handle this case.
02820                                 $defaultModules['user'] = 'ready';
02821                         }
02822                 } else {
02823                         // User JS disabled
02824                         $defaultModules['user'] = 'missing';
02825                 }
02826 
02827                 // Group JS is only enabled if site JS is enabled.
02828                 if ( $wgUseSiteJs ) {
02829                         if ( $this->getUser()->isLoggedIn() ) {
02830                                 $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
02831                                         /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02832                                 );
02833                                 $defaultModules['user.groups'] = 'loading';
02834                         } else {
02835                                 // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to
02836                                 // avoid blocking gadgets that might depend upon the module.
02837                                 $defaultModules['user.groups'] = 'ready';
02838                         }
02839                 } else {
02840                         // Site (and group JS) disabled
02841                         $defaultModules['user.groups'] = 'missing';
02842                 }
02843 
02844                 $loaderInit = '';
02845                 if ( $inHead ) {
02846                         // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'.
02847                         foreach ( $defaultModules as $m => $state ) {
02848                                 if ( $state == 'loading' ) {
02849                                         unset( $defaultModules[$m] );
02850                                 }
02851                         }
02852                 }
02853                 if ( count( $defaultModules ) > 0 ) {
02854                         $loaderInit = Html::inlineScript(
02855                                 ResourceLoader::makeLoaderConditionalScript(
02856                                         ResourceLoader::makeLoaderStateScript( $defaultModules )
02857                                 )
02858                         ) . "\n";
02859                 }
02860                 return $loaderInit . $scripts;
02861         }
02862 
02867         function getBottomScripts() {
02868                 global $wgResourceLoaderExperimentalAsyncLoading;
02869                 if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
02870                         return $this->getScriptsForBottomQueue( false );
02871                 } else {
02872                         return '';
02873                 }
02874         }
02875 
02882         public function addJsConfigVars( $keys, $value = null ) {
02883                 if ( is_array( $keys ) ) {
02884                         foreach ( $keys as $key => $value ) {
02885                                 $this->mJsConfigVars[$key] = $value;
02886                         }
02887                         return;
02888                 }
02889 
02890                 $this->mJsConfigVars[$keys] = $value;
02891         }
02892 
02893 
02906         public function getJSVars() {
02907                 global $wgUseAjax, $wgContLang;
02908 
02909                 $latestRevID = 0;
02910                 $pageID = 0;
02911                 $canonicalName = false; # bug 21115
02912 
02913                 $title = $this->getTitle();
02914                 $ns = $title->getNamespace();
02915                 $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
02916 
02917                 // Get the relevant title so that AJAX features can use the correct page name
02918                 // when making API requests from certain special pages (bug 34972).
02919                 $relevantTitle = $this->getSkin()->getRelevantTitle();
02920 
02921                 if ( $ns == NS_SPECIAL ) {
02922                         list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
02923                 } elseif ( $this->canUseWikiPage() ) {
02924                         $wikiPage = $this->getWikiPage();
02925                         $latestRevID = $wikiPage->getLatest();
02926                         $pageID = $wikiPage->getId();
02927                 }
02928 
02929                 $lang = $title->getPageLanguage();
02930 
02931                 // Pre-process information
02932                 $separatorTransTable = $lang->separatorTransformTable();
02933                 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
02934                 $compactSeparatorTransTable = array(
02935                         implode( "\t", array_keys( $separatorTransTable ) ),
02936                         implode( "\t", $separatorTransTable ),
02937                 );
02938                 $digitTransTable = $lang->digitTransformTable();
02939                 $digitTransTable = $digitTransTable ? $digitTransTable : array();
02940                 $compactDigitTransTable = array(
02941                         implode( "\t", array_keys( $digitTransTable ) ),
02942                         implode( "\t", $digitTransTable ),
02943                 );
02944 
02945                 $vars = array(
02946                         'wgCanonicalNamespace' => $nsname,
02947                         'wgCanonicalSpecialPageName' => $canonicalName,
02948                         'wgNamespaceNumber' => $title->getNamespace(),
02949                         'wgPageName' => $title->getPrefixedDBKey(),
02950                         'wgTitle' => $title->getText(),
02951                         'wgCurRevisionId' => $latestRevID,
02952                         'wgArticleId' => $pageID,
02953                         'wgIsArticle' => $this->isArticle(),
02954                         'wgAction' => Action::getActionName( $this->getContext() ),
02955                         'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(),
02956                         'wgUserGroups' => $this->getUser()->getEffectiveGroups(),
02957                         'wgCategories' => $this->getCategories(),
02958                         'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
02959                         'wgPageContentLanguage' => $lang->getCode(),
02960                         'wgSeparatorTransformTable' => $compactSeparatorTransTable,
02961                         'wgDigitTransformTable' => $compactDigitTransTable,
02962                         'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
02963                         'wgMonthNames' => $lang->getMonthNamesArray(),
02964                         'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
02965                         'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(),
02966                 );
02967                 if ( $wgContLang->hasVariants() ) {
02968                         $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
02969                 }
02970                 foreach ( $title->getRestrictionTypes() as $type ) {
02971                         $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
02972                 }
02973                 if ( $title->isMainPage() ) {
02974                         $vars['wgIsMainPage'] = true;
02975                 }
02976                 if ( $this->mRedirectedFrom ) {
02977                         $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey();
02978                 }
02979 
02980                 // Allow extensions to add their custom variables to the mw.config map.
02981                 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
02982                 // page-dependant but site-wide (without state).
02983                 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
02984                 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
02985 
02986                 // Merge in variables from addJsConfigVars last
02987                 return array_merge( $vars, $this->mJsConfigVars );
02988         }
02989 
02999         public function userCanPreview() {
03000                 if ( $this->getRequest()->getVal( 'action' ) != 'submit'
03001                         || !$this->getRequest()->wasPosted()
03002                         || !$this->getUser()->matchEditToken(
03003                                 $this->getRequest()->getVal( 'wpEditToken' ) )
03004                 ) {
03005                         return false;
03006                 }
03007                 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
03008                         return false;
03009                 }
03010 
03011                 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
03012         }
03013 
03019         public function getHeadLinksArray( $addContentType = false ) {
03020                 global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
03021                         $wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
03022                         $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
03023                         $wgDisableLangConversion, $wgCanonicalLanguageLinks,
03024                         $wgRightsPage, $wgRightsUrl;
03025 
03026                 $tags = array();
03027 
03028                 if ( $addContentType ) {
03029                         if ( $wgHtml5 ) {
03030                                 # More succinct than <meta http-equiv=Content-Type>, has the
03031                                 # same effect
03032                                 $tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
03033                         } else {
03034                                 $tags['meta-content-type'] = Html::element( 'meta', array(
03035                                         'http-equiv' => 'Content-Type',
03036                                         'content' => "$wgMimeType; charset=UTF-8"
03037                                 ) );
03038                                 $tags['meta-content-style-type'] = Html::element( 'meta', array(  // bug 15835
03039                                         'http-equiv' => 'Content-Style-Type',
03040                                         'content' => 'text/css'
03041                                 ) );
03042                         }
03043                 }
03044 
03045                 $tags['meta-generator'] = Html::element( 'meta', array(
03046                         'name' => 'generator',
03047                         'content' => "MediaWiki $wgVersion",
03048                 ) );
03049 
03050                 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
03051                 if( $p !== 'index,follow' ) {
03052                         // http://www.robotstxt.org/wc/meta-user.html
03053                         // Only show if it's different from the default robots policy
03054                         $tags['meta-robots'] = Html::element( 'meta', array(
03055                                 'name' => 'robots',
03056                                 'content' => $p,
03057                         ) );
03058                 }
03059 
03060                 if ( count( $this->mKeywords ) > 0 ) {
03061                         $strip = array(
03062                                 "/<.*?" . ">/" => '',
03063                                 "/_/" => ' '
03064                         );
03065                         $tags['meta-keywords'] = Html::element( 'meta', array(
03066                                 'name' => 'keywords',
03067                                 'content' =>  preg_replace(
03068                                         array_keys( $strip ),
03069                                         array_values( $strip ),
03070                                         implode( ',', $this->mKeywords )
03071                                 )
03072                         ) );
03073                 }
03074 
03075                 foreach ( $this->mMetatags as $tag ) {
03076                         if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
03077                                 $a = 'http-equiv';
03078                                 $tag[0] = substr( $tag[0], 5 );
03079                         } else {
03080                                 $a = 'name';
03081                         }
03082                         $tagName = "meta-{$tag[0]}";
03083                         if ( isset( $tags[$tagName] ) ) {
03084                                 $tagName .= $tag[1];
03085                         }
03086                         $tags[$tagName] = Html::element( 'meta',
03087                                 array(
03088                                         $a => $tag[0],
03089                                         'content' => $tag[1]
03090                                 )
03091                         );
03092                 }
03093 
03094                 foreach ( $this->mLinktags as $tag ) {
03095                         $tags[] = Html::element( 'link', $tag );
03096                 }
03097 
03098                 # Universal edit button
03099                 if ( $wgUniversalEditButton && $this->isArticleRelated() ) {
03100                         $user = $this->getUser();
03101                         if ( $this->getTitle()->quickUserCan( 'edit', $user )
03102                                 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
03103                                 // Original UniversalEditButton
03104                                 $msg = $this->msg( 'edit' )->text();
03105                                 $tags['universal-edit-button'] = Html::element( 'link', array(
03106                                         'rel' => 'alternate',
03107                                         'type' => 'application/x-wiki',
03108                                         'title' => $msg,
03109                                         'href' => $this->getTitle()->getLocalURL( 'action=edit' )
03110                                 ) );
03111                                 // Alternate edit link
03112                                 $tags['alternative-edit'] = Html::element( 'link', array(
03113                                         'rel' => 'edit',
03114                                         'title' => $msg,
03115                                         'href' => $this->getTitle()->getLocalURL( 'action=edit' )
03116                                 ) );
03117                         }
03118                 }
03119 
03120                 # Generally the order of the favicon and apple-touch-icon links
03121                 # should not matter, but Konqueror (3.5.9 at least) incorrectly
03122                 # uses whichever one appears later in the HTML source. Make sure
03123                 # apple-touch-icon is specified first to avoid this.
03124                 if ( $wgAppleTouchIcon !== false ) {
03125                         $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
03126                 }
03127 
03128                 if ( $wgFavicon !== false ) {
03129                         $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
03130                 }
03131 
03132                 # OpenSearch description link
03133                 $tags['opensearch'] = Html::element( 'link', array(
03134                         'rel' => 'search',
03135                         'type' => 'application/opensearchdescription+xml',
03136                         'href' => wfScript( 'opensearch_desc' ),
03137                         'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
03138                 ) );
03139 
03140                 if ( $wgEnableAPI ) {
03141                         # Real Simple Discovery link, provides auto-discovery information
03142                         # for the MediaWiki API (and potentially additional custom API
03143                         # support such as WordPress or Twitter-compatible APIs for a
03144                         # blogging extension, etc)
03145                         $tags['rsd'] = Html::element( 'link', array(
03146                                 'rel' => 'EditURI',
03147                                 'type' => 'application/rsd+xml',
03148                                 // Output a protocol-relative URL here if $wgServer is protocol-relative
03149                                 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though
03150                                 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ),
03151                         ) );
03152                 }
03153 
03154 
03155                 # Language variants
03156                 if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) {
03157                         $lang = $this->getTitle()->getPageLanguage();
03158                         if ( $lang->hasVariants() ) {
03159 
03160                                 $urlvar = $lang->getURLVariant();
03161 
03162                                 if ( !$urlvar ) {
03163                                         $variants = $lang->getVariants();
03164                                         foreach ( $variants as $_v ) {
03165                                                 $tags["variant-$_v"] = Html::element( 'link', array(
03166                                                         'rel' => 'alternate',
03167                                                         'hreflang' => $_v,
03168                                                         'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
03169                                                 );
03170                                         }
03171                                 } else {
03172                                         $tags['canonical'] = Html::element( 'link', array(
03173                                                 'rel' => 'canonical',
03174                                                 'href' => $this->getTitle()->getCanonicalUrl()
03175                                         ) );
03176                                 }
03177                         }
03178                 }
03179 
03180                 # Copyright
03181                 $copyright = '';
03182                 if ( $wgRightsPage ) {
03183                         $copy = Title::newFromText( $wgRightsPage );
03184 
03185                         if ( $copy ) {
03186                                 $copyright = $copy->getLocalURL();
03187                         }
03188                 }
03189 
03190                 if ( !$copyright && $wgRightsUrl ) {
03191                         $copyright = $wgRightsUrl;
03192                 }
03193 
03194                 if ( $copyright ) {
03195                         $tags['copyright'] = Html::element( 'link', array(
03196                                 'rel' => 'copyright',
03197                                 'href' => $copyright )
03198                         );
03199                 }
03200 
03201                 # Feeds
03202                 if ( $wgFeed ) {
03203                         foreach( $this->getSyndicationLinks() as $format => $link ) {
03204                                 # Use the page name for the title.  In principle, this could
03205                                 # lead to issues with having the same name for different feeds
03206                                 # corresponding to the same page, but we can't avoid that at
03207                                 # this low a level.
03208 
03209                                 $tags[] = $this->feedLink(
03210                                         $format,
03211                                         $link,
03212                                         # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
03213                                         $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text()
03214                                 );
03215                         }
03216 
03217                         # Recent changes feed should appear on every page (except recentchanges,
03218                         # that would be redundant). Put it after the per-page feed to avoid
03219                         # changing existing behavior. It's still available, probably via a
03220                         # menu in your browser. Some sites might have a different feed they'd
03221                         # like to promote instead of the RC feed (maybe like a "Recent New Articles"
03222                         # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
03223                         # If so, use it instead.
03224                         if ( $wgOverrideSiteFeed ) {
03225                                 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
03226                                         // Note, this->feedLink escapes the url.
03227                                         $tags[] = $this->feedLink(
03228                                                 $type,
03229                                                 $feedUrl,
03230                                                 $this->msg( "site-{$type}-feed", $wgSitename )->text()
03231                                         );
03232                                 }
03233                         } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
03234                                 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
03235                                 foreach ( $wgAdvertisedFeedTypes as $format ) {
03236                                         $tags[] = $this->feedLink(
03237                                                 $format,
03238                                                 $rctitle->getLocalURL( "feed={$format}" ),
03239                                                 $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'.
03240                                         );
03241                                 }
03242                         }
03243                 }
03244                 return $tags;
03245         }
03246 
03253         public function getHeadLinks( $unused = null, $addContentType = false ) {
03254                 return implode( "\n", $this->getHeadLinksArray( $addContentType ) );
03255         }
03256 
03265         private function feedLink( $type, $url, $text ) {
03266                 return Html::element( 'link', array(
03267                         'rel' => 'alternate',
03268                         'type' => "application/$type+xml",
03269                         'title' => $text,
03270                         'href' => $url )
03271                 );
03272         }
03273 
03283         public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
03284                 $options = array();
03285                 // Even though we expect the media type to be lowercase, but here we
03286                 // force it to lowercase to be safe.
03287                 if( $media ) {
03288                         $options['media'] = $media;
03289                 }
03290                 if( $condition ) {
03291                         $options['condition'] = $condition;
03292                 }
03293                 if( $dir ) {
03294                         $options['dir'] = $dir;
03295                 }
03296                 $this->styles[$style] = $options;
03297         }
03298 
03304         public function addInlineStyle( $style_css, $flip = 'noflip' ) {
03305                 if( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
03306                         # If wanted, and the interface is right-to-left, flip the CSS
03307                         $style_css = CSSJanus::transform( $style_css, true, false );
03308                 }
03309                 $this->mInlineStyles .= Html::inlineStyle( $style_css );
03310         }
03311 
03318         public function buildCssLinks() {
03319                 global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs,
03320                         $wgLang, $wgContLang;
03321 
03322                 $this->getSkin()->setupSkinUserCss( $this );
03323 
03324                 // Add ResourceLoader styles
03325                 // Split the styles into four groups
03326                 $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() );
03327                 $otherTags = ''; // Tags to append after the normal <link> tags
03328                 $resourceLoader = $this->getResourceLoader();
03329 
03330                 $moduleStyles = $this->getModuleStyles();
03331 
03332                 // Per-site custom styles
03333                 if ( $wgUseSiteCss ) {
03334                         $moduleStyles[] = 'site';
03335                         $moduleStyles[] = 'noscript';
03336                         if( $this->getUser()->isLoggedIn() ){
03337                                 $moduleStyles[] = 'user.groups';
03338                         }
03339                 }
03340 
03341                 // Per-user custom styles
03342                 if ( $wgAllowUserCss ) {
03343                         if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
03344                                 // We're on a preview of a CSS subpage
03345                                 // Exclude this page from the user module in case it's in there (bug 26283)
03346                                 $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
03347                                         array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
03348                                 );
03349 
03350                                 // Load the previewed CSS
03351                                 // If needed, Janus it first. This is user-supplied CSS, so it's
03352                                 // assumed to be right for the content language directionality.
03353                                 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
03354                                 if ( $wgLang->getDir() !== $wgContLang->getDir() ) {
03355                                         $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
03356                                 }
03357                                 $otherTags .= Html::inlineStyle( $previewedCSS );
03358                         } else {
03359                                 // Load the user styles normally
03360                                 $moduleStyles[] = 'user';
03361                         }
03362                 }
03363 
03364                 // Per-user preference styles
03365                 if ( $wgAllowUserCssPrefs ) {
03366                         $moduleStyles[] = 'user.cssprefs';
03367                 }
03368 
03369                 foreach ( $moduleStyles as $name ) {
03370                         $module = $resourceLoader->getModule( $name );
03371                         if ( !$module ) {
03372                                 continue;
03373                         }
03374                         $group = $module->getGroup();
03375                         // Modules in groups named "other" or anything different than "user", "site" or "private"
03376                         // will be placed in the "other" group
03377                         $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
03378                 }
03379 
03380                 // We want site, private and user styles to override dynamically added styles from modules, but we want
03381                 // dynamically added styles to override statically added styles from other modules. So the order
03382                 // has to be other, dynamic, site, private, user
03383                 // Add statically added styles for other modules
03384                 $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES );
03385                 // Add normal styles added through addStyle()/addInlineStyle() here
03386                 $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
03387                 // Add marker tag to mark the place where the client-side loader should inject dynamic styles
03388                 // We use a <meta> tag with a made-up name for this because that's valid HTML
03389                 $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n";
03390 
03391                 // Add site, private and user styles
03392                 // 'private' at present only contains user.options, so put that before 'user'
03393                 // Any future private modules will likely have a similar user-specific character
03394                 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
03395                         $ret .= $this->makeResourceLoaderLink( $styles[$group],
03396                                         ResourceLoaderModule::TYPE_STYLES
03397                         );
03398                 }
03399 
03400                 // Add stuff in $otherTags (previewed user CSS if applicable)
03401                 $ret .= $otherTags;
03402                 return $ret;
03403         }
03404 
03408         public function buildCssLinksArray() {
03409                 $links = array();
03410 
03411                 // Add any extension CSS
03412                 foreach ( $this->mExtStyles as $url ) {
03413                         $this->addStyle( $url );
03414                 }
03415                 $this->mExtStyles = array();
03416 
03417                 foreach( $this->styles as $file => $options ) {
03418                         $link = $this->styleLink( $file, $options );
03419                         if( $link ) {
03420                                 $links[$file] = $link;
03421                         }
03422                 }
03423                 return $links;
03424         }
03425 
03434         protected function styleLink( $style, $options ) {
03435                 if( isset( $options['dir'] ) ) {
03436                         if( $this->getLanguage()->getDir() != $options['dir'] ) {
03437                                 return '';
03438                         }
03439                 }
03440 
03441                 if( isset( $options['media'] ) ) {
03442                         $media = self::transformCssMedia( $options['media'] );
03443                         if( is_null( $media ) ) {
03444                                 return '';
03445                         }
03446                 } else {
03447                         $media = 'all';
03448                 }
03449 
03450                 if( substr( $style, 0, 1 ) == '/' ||
03451                         substr( $style, 0, 5 ) == 'http:' ||
03452                         substr( $style, 0, 6 ) == 'https:' ) {
03453                         $url = $style;
03454                 } else {
03455                         global $wgStylePath, $wgStyleVersion;
03456                         $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
03457                 }
03458 
03459                 $link = Html::linkedStyle( $url, $media );
03460 
03461                 if( isset( $options['condition'] ) ) {
03462                         $condition = htmlspecialchars( $options['condition'] );
03463                         $link = "<!--[if $condition]>$link<![endif]-->";
03464                 }
03465                 return $link;
03466         }
03467 
03474         public static function transformCssMedia( $media ) {
03475                 global $wgRequest, $wgHandheldForIPhone;
03476 
03477                 // Switch in on-screen display for media testing
03478                 $switches = array(
03479                         'printable' => 'print',
03480                         'handheld' => 'handheld',
03481                 );
03482                 foreach( $switches as $switch => $targetMedia ) {
03483                         if( $wgRequest->getBool( $switch ) ) {
03484                                 if( $media == $targetMedia ) {
03485                                         $media = '';
03486                                 } elseif( $media == 'screen' ) {
03487                                         return null;
03488                                 }
03489                         }
03490                 }
03491 
03492                 // Expand longer media queries as iPhone doesn't grok 'handheld'
03493                 if( $wgHandheldForIPhone ) {
03494                         $mediaAliases = array(
03495                                 'screen' => 'screen and (min-device-width: 481px)',
03496                                 'handheld' => 'handheld, only screen and (max-device-width: 480px)',
03497                         );
03498 
03499                         if( isset( $mediaAliases[$media] ) ) {
03500                                 $media = $mediaAliases[$media];
03501                         }
03502                 }
03503 
03504                 return $media;
03505         }
03506 
03513         public function addWikiMsg( /*...*/ ) {
03514                 $args = func_get_args();
03515                 $name = array_shift( $args );
03516                 $this->addWikiMsgArray( $name, $args );
03517         }
03518 
03527         public function addWikiMsgArray( $name, $args ) {
03528                 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
03529         }
03530 
03554         public function wrapWikiMsg( $wrap /*, ...*/ ) {
03555                 $msgSpecs = func_get_args();
03556                 array_shift( $msgSpecs );
03557                 $msgSpecs = array_values( $msgSpecs );
03558                 $s = $wrap;
03559                 foreach ( $msgSpecs as $n => $spec ) {
03560                         $options = array();
03561                         if ( is_array( $spec ) ) {
03562                                 $args = $spec;
03563                                 $name = array_shift( $args );
03564                                 if ( isset( $args['options'] ) ) {
03565                                         unset( $args['options'] );
03566                                         wfDeprecated(
03567                                                 'Adding "options" to ' . __METHOD__ . ' is no longer supported',
03568                                                 '1.20'
03569                                         );
03570                                 }
03571                         }  else {
03572                                 $args = array();
03573                                 $name = $spec;
03574                         }
03575                         $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
03576                 }
03577                 $this->addWikiText( $s );
03578         }
03579 
03589         public function includeJQuery( $modules = array() ) {
03590                 return array();
03591         }
03592 
03593 }